// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. /* eslint-disable */ /* eslint-enable prettier/prettier*/ import { ReactiveSocket } from "rsocket-types"; import { ClientOS } from "./clientUtils"; import { InspectorView, InspectorViewType } from "./views/inspectorView"; import { OutputChannelLogger } from "../log/OutputChannelLogger"; import { InspectorViewFactory } from "./views/inspectorViewFactory"; /** * @preserve * Start region: the code is borrowed from https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/Client.tsx * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ export interface ClientIdConstituents { app: string; os: ClientOS; device: string; device_id: string; } export interface UninitializedClient { os: ClientOS; deviceName: string; appName: string; } export interface ClientCsrQuery { csr?: string; csr_path?: string; } export interface ClientQuery extends ClientIdConstituents { sdk_version?: number; } export interface SecureClientQuery extends ClientQuery, ClientCsrQuery { medium?: number; } export interface RequestParams { api: string; method: string; params?: Record; } type RequestMetadata = { method: string; id: number; params: RequestParams | undefined }; type ErrorType = { message: string; stacktrace: string; name: string }; export class ClientDevice { private readonly networkInspectorPluginName: string; private _id: string; private _query: ClientQuery; private _connection: ReactiveSocket | null | undefined; private logger: OutputChannelLogger; private messageIdCounter: number; private sdkVersion: number; private activePlugins: Set; private inspectorView: InspectorView; private requestCallbacks: Map< number, { resolve: (data: any) => void; reject: (err: Error) => void; metadata: RequestMetadata; } >; constructor( id: string, query: ClientQuery, connection: ReactiveSocket | null | undefined, inspectorViewType: InspectorViewType, logger: OutputChannelLogger, ) { this.networkInspectorPluginName = "Network"; this._id = id; this._query = query; this._connection = connection; this.logger = logger; this.messageIdCounter = 0; this.sdkVersion = query.sdk_version || 0; this.activePlugins = new Set(); this.requestCallbacks = new Map(); this.inspectorView = InspectorViewFactory.getInspectorView(inspectorViewType); } get id(): string { return this._id; } get query(): ClientQuery { return this._query; } get connection(): ReactiveSocket | null | undefined { return this._connection; } public async init(): Promise { const plugins = await this.loadPlugins(); if (plugins.includes(this.networkInspectorPluginName)) { this.initNetworkInspectorPlugin(); } await this.inspectorView.init(); } public onMessage(msg: string): void { if (typeof msg !== "string") { return; } let rawData; try { rawData = JSON.parse(msg); } catch (err) { this.logger.error(`Invalid JSON: ${msg}`); return; } const data: { id?: number; method?: string; params?: RequestParams; success?: Record; error?: ErrorType; } = rawData; if (!data.id) { const { error } = data; if (error) { this.logger.error( `Error received from device ${ data.method ? `when calling ${data.method}` : "" }: ${error.message} + \nDevice Stack Trace: ${error.stacktrace}`, ); } else if (data.method === "execute" && data.params) { this.inspectorView.handleMessage(data.params); } return; // method === "execute"; } if (this.sdkVersion < 1) { const callbacks = this.requestCallbacks.get(data.id); if (!callbacks) { return; } this.requestCallbacks.delete(data.id); this.onResponse(data, callbacks.resolve, callbacks.reject); } } private initNetworkInspectorPlugin(): void { this.activePlugins.add(this.networkInspectorPluginName); this.rawSend("init", { plugin: this.networkInspectorPluginName }); } private async loadPlugins(): Promise { const plugins = await this.rawCall<{ plugins: string[] }>("getPlugins", false).then( data => data.plugins, ); return plugins; } private rawSend(method: string, params?: Record): void { const data = { method, params, }; if (this._connection) { this._connection.fireAndForget({ data: JSON.stringify(data) }); } } private rawCall(method: string, fromPlugin: boolean, params?: RequestParams): Promise { return new Promise((resolve, reject) => { const id = this.messageIdCounter++; const metadata: RequestMetadata = { method, id, params, }; if (this.sdkVersion < 1) { this.requestCallbacks.set(id, { reject, resolve, metadata }); } const data = { id, method, params, }; const plugin = params ? params.api : undefined; if (this.sdkVersion < 1) { if (this._connection) { this._connection.fireAndForget({ data: JSON.stringify(data) }); } return; } if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) { this._connection!.requestResponse({ data: JSON.stringify(data), }).subscribe({ onComplete: payload => { if (!fromPlugin || this.isAcceptingMessagesFromPlugin(plugin)) { const response: { success?: Record; error?: ErrorType; } = JSON.parse(payload.data); this.onResponse(response, resolve, reject); } }, onError: e => { reject(e); }, }); } else { reject( new Error( `Cannot send ${method}, client is not accepting messages for plugin ${plugin}`, ), ); } }); } private onResponse( data: { success?: Record; error?: ErrorType; }, resolve: ((a: any) => any) | undefined, reject: (error: ErrorType) => any, ): void { if (data.success) { resolve && resolve(data.success); } else if (data.error) { reject(data.error); const { error } = data; if (error) { this.logger.debug(error.message); } } } private isAcceptingMessagesFromPlugin(plugin: string | null | undefined): boolean { return !!(this._connection && (!plugin || this.activePlugins.has(plugin))); } } /** * @preserve * End region: https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/Client.tsx */