Files
Alex HuntandFacebook GitHub Bot 7f57018e25 Add profiling_target_registered event, log to terminal (#47968)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/47968

Updates the Inspector Proxy to report + log when a profiling build target (experimental) is registered. This notifies the developer that debugging is available for these app(s), which will not otherwise fetch development bundles from Metro.

Changelog: [Internal]

Reviewed By: rubennorte

Differential Revision: D66501771

fbshipit-source-id: e06dee279158094ad5c70bf8e6a90e7c983de48a
2024-11-27 11:42:31 -08:00

247 lines
7.1 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {EventReporter} from '../types/EventReporter';
import type {CDPResponse} from './cdp-types/messages';
import type {DeepReadOnly} from './types';
import TTLCache from '@isaacs/ttlcache';
type PendingCommand = {
method: string,
requestOrigin: 'proxy' | 'debugger',
requestTime: number,
metadata: RequestMetadata,
};
type DeviceMetadata = $ReadOnly<{
appId: string,
deviceId: string,
deviceName: string,
}>;
type RequestMetadata = $ReadOnly<{
pageId: string | null,
frontendUserAgent: string | null,
prefersFuseboxFrontend: boolean | null,
}>;
type ResponseMetadata = $ReadOnly<{
pageId: string | null,
frontendUserAgent: string | null,
prefersFuseboxFrontend: boolean | null,
}>;
class DeviceEventReporter {
#eventReporter: EventReporter;
#pendingCommands: TTLCache<number, PendingCommand> = new TTLCache({
ttl: 10000,
dispose: (
command: PendingCommand,
id: number,
reason: 'evict' | 'set' | 'delete' | 'stale',
) => {
if (reason === 'delete' || reason === 'set') {
// TODO: Report clobbering ('set') using a dedicated error code
return;
}
this.#logExpiredCommand(command);
},
});
#metadata: DeviceMetadata;
constructor(eventReporter: EventReporter, metadata: DeviceMetadata) {
this.#eventReporter = eventReporter;
this.#metadata = metadata;
}
logRequest(
req: $ReadOnly<{id: number, method: string, ...}>,
origin: 'debugger' | 'proxy',
metadata: RequestMetadata,
): void {
this.#pendingCommands.set(req.id, {
method: req.method,
requestOrigin: origin,
requestTime: Date.now(),
metadata,
});
}
logResponse(
res: DeepReadOnly<CDPResponse<>>,
origin: 'device' | 'proxy',
metadata: ResponseMetadata,
): void {
const pendingCommand = this.#pendingCommands.get(res.id);
if (!pendingCommand) {
this.#eventReporter.logEvent({
type: 'debugger_command',
protocol: 'CDP',
requestOrigin: null,
method: null,
status: 'coded_error',
errorCode: 'UNMATCHED_REQUEST_ID',
responseOrigin: 'proxy',
timeSinceStart: null,
appId: this.#metadata.appId,
deviceId: this.#metadata.deviceId,
deviceName: this.#metadata.deviceName,
pageId: metadata.pageId,
frontendUserAgent: metadata.frontendUserAgent,
prefersFuseboxFrontend: metadata.prefersFuseboxFrontend,
});
return;
}
const timeSinceStart = Date.now() - pendingCommand.requestTime;
this.#pendingCommands.delete(res.id);
if (res.error) {
let {message} = res.error;
if ('data' in res.error) {
message += ` (${String(res.error.data)})`;
}
this.#eventReporter.logEvent({
type: 'debugger_command',
requestOrigin: pendingCommand.requestOrigin,
method: pendingCommand.method,
protocol: 'CDP',
status: 'coded_error',
errorCode: 'PROTOCOL_ERROR',
errorDetails: message,
responseOrigin: origin,
timeSinceStart,
appId: this.#metadata.appId,
deviceId: this.#metadata.deviceId,
deviceName: this.#metadata.deviceName,
pageId: pendingCommand.metadata.pageId,
frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
prefersFuseboxFrontend: metadata.prefersFuseboxFrontend,
});
return;
}
this.#eventReporter.logEvent({
type: 'debugger_command',
protocol: 'CDP',
requestOrigin: pendingCommand.requestOrigin,
method: pendingCommand.method,
status: 'success',
responseOrigin: origin,
timeSinceStart,
appId: this.#metadata.appId,
deviceId: this.#metadata.deviceId,
deviceName: this.#metadata.deviceName,
pageId: pendingCommand.metadata.pageId,
frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
prefersFuseboxFrontend: metadata.prefersFuseboxFrontend,
});
}
logProfilingTargetRegistered() {
this.#eventReporter.logEvent({
type: 'profiling_target_registered',
status: 'success',
appId: this.#metadata.appId,
deviceName: this.#metadata.deviceName,
deviceId: this.#metadata.deviceId,
pageId: null,
});
}
logConnection(
connectedEntity: 'debugger',
metadata: $ReadOnly<{
pageId: string,
frontendUserAgent: string | null,
}>,
) {
this.#eventReporter.logEvent({
type: 'connect_debugger_frontend',
status: 'success',
appId: this.#metadata.appId,
deviceName: this.#metadata.deviceName,
deviceId: this.#metadata.deviceId,
pageId: metadata.pageId,
frontendUserAgent: metadata.frontendUserAgent,
});
}
logDisconnection(disconnectedEntity: 'device' | 'debugger') {
const eventReporter = this.#eventReporter;
if (!eventReporter) {
return;
}
const errorCode =
disconnectedEntity === 'device'
? 'DEVICE_DISCONNECTED'
: 'DEBUGGER_DISCONNECTED';
for (const pendingCommand of this.#pendingCommands.values()) {
this.#eventReporter.logEvent({
type: 'debugger_command',
protocol: 'CDP',
requestOrigin: pendingCommand.requestOrigin,
method: pendingCommand.method,
status: 'coded_error',
errorCode,
responseOrigin: 'proxy',
timeSinceStart: Date.now() - pendingCommand.requestTime,
appId: this.#metadata.appId,
deviceId: this.#metadata.deviceId,
deviceName: this.#metadata.deviceName,
pageId: pendingCommand.metadata.pageId,
frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
prefersFuseboxFrontend: pendingCommand.metadata.prefersFuseboxFrontend,
});
}
this.#pendingCommands.clear();
}
logProxyMessageHandlingError(
messageOrigin: 'device' | 'debugger',
error: Error,
message: string,
): void {
this.#eventReporter.logEvent({
type: 'proxy_error',
status: 'error',
messageOrigin,
message,
error: error.message,
errorStack: error.stack,
appId: this.#metadata.appId,
deviceId: this.#metadata.deviceId,
deviceName: this.#metadata.deviceName,
pageId: null,
});
}
#logExpiredCommand(pendingCommand: PendingCommand): void {
this.#eventReporter.logEvent({
type: 'debugger_command',
protocol: 'CDP',
requestOrigin: pendingCommand.requestOrigin,
method: pendingCommand.method,
status: 'coded_error',
errorCode: 'TIMED_OUT',
responseOrigin: 'proxy',
timeSinceStart: Date.now() - pendingCommand.requestTime,
appId: this.#metadata.appId,
deviceId: this.#metadata.deviceId,
deviceName: this.#metadata.deviceName,
pageId: pendingCommand.metadata.pageId,
frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
prefersFuseboxFrontend: pendingCommand.metadata.prefersFuseboxFrontend,
});
}
}
export default DeviceEventReporter;