Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dev-middleware/src/createDevMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default function createDevMiddleware({
serverBaseUrl,
eventReporter,
experiments,
logger,
unstable_customInspectorMessageHandler,
);

Expand Down
70 changes: 53 additions & 17 deletions packages/dev-middleware/src/inspector-proxy/InspectorProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import type {EventReporter} from '../types/EventReporter';
import type {Experiments} from '../types/Experiments';
import type {Logger} from '../types/Logger';
import type {CreateCustomMessageHandlerFn} from './CustomMessageHandler';
import type {DeviceOptions} from './Device';
import type {
Expand Down Expand Up @@ -75,18 +76,22 @@ export default class InspectorProxy implements InspectorProxyQueries {
// custom message handler factory allowing implementers to handle unsupported CDP messages.
#customMessageHandler: ?CreateCustomMessageHandlerFn;

#logger: ?Logger;

constructor(
projectRoot: string,
serverBaseUrl: string,
eventReporter: ?EventReporter,
experiments: Experiments,
logger?: Logger,
customMessageHandler: ?CreateCustomMessageHandlerFn,
) {
this.#projectRoot = projectRoot;
this.#serverBaseUrl = new URL(serverBaseUrl);
this.#devices = new Map();
this.#eventReporter = eventReporter;
this.#experiments = experiments;
this.#logger = logger;
this.#customMessageHandler = customMessageHandler;
}

Expand Down Expand Up @@ -218,15 +223,15 @@ export default class InspectorProxy implements InspectorProxyQueries {
});
// $FlowFixMe[value-as-type]
wss.on('connection', async (socket: WS, req) => {
try {
const fallbackDeviceId = String(this.#deviceCounter++);
const fallbackDeviceId = String(this.#deviceCounter++);

const query = url.parse(req.url || '', true).query || {};
const deviceId = query.device || fallbackDeviceId;
const deviceName = query.name || 'Unknown';
const appName = query.app || 'Unknown';
const isProfilingBuild = query.profiling === 'true';
const query = url.parse(req.url || '', true).query || {};
const deviceId = query.device || fallbackDeviceId;
const deviceName = query.name || 'Unknown';
const appName = query.app || 'Unknown';
const isProfilingBuild = query.profiling === 'true';

try {
const deviceRelativeBaseUrl =
getBaseUrlFromRequest(req) ?? this.#serverBaseUrl;

Expand Down Expand Up @@ -255,19 +260,37 @@ export default class InspectorProxy implements InspectorProxyQueries {

this.#devices.set(deviceId, newDevice);

this.#logger?.info(
"Connection established to app '%s' on device '%s'.",
appName,
deviceName,
);

debug(
`Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}, via=${deviceRelativeBaseUrl.origin}`,
);

socket.on('close', () => {
socket.on('close', (code: number, reason: string) => {
this.#logger?.info(
"Connection closed to app '%s' on device '%s' with code '%s' and reason '%s'.",
appName,
deviceName,
String(code),
reason,
);

if (this.#devices.get(deviceId)?.dangerouslyGetSocket() === socket) {
this.#devices.delete(deviceId);
}
debug(`Device ${deviceName} disconnected.`);
});
} catch (e) {
console.error('error', e);
socket.close(INTERNAL_ERROR_CODE, e?.toString() ?? 'Unknown error');
} catch (error) {
this.#logger?.error(
"Connection failed to be established with app '%s' on device '%s' with error:",
appName,
deviceName,
error,
);
socket.close(INTERNAL_ERROR_CODE, error?.toString() ?? 'Unknown error');
}
});
return wss;
Expand Down Expand Up @@ -304,19 +327,24 @@ export default class InspectorProxy implements InspectorProxyQueries {
throw new Error('Unknown device with ID ' + deviceId);
}

this.#logger?.info('Connection to DevTools established.');

this.#startHeartbeat(socket, DEBUGGER_HEARTBEAT_INTERVAL_MS);

device.handleDebuggerConnection(socket, pageId, {
debuggerRelativeBaseUrl,
userAgent: req.headers['user-agent'] ?? query.userAgent ?? null,
});
} catch (e) {
console.error(e);
socket.close(INTERNAL_ERROR_CODE, e?.toString() ?? 'Unknown error');
} catch (error) {
this.#logger?.error(
'Connection failed to be established with DevTools with error:',
error,
);
socket.close(INTERNAL_ERROR_CODE, error?.toString() ?? 'Unknown error');
this.#eventReporter?.logEvent({
type: 'connect_debugger_frontend',
status: 'error',
error: e,
error,
});
}
});
Expand Down Expand Up @@ -363,6 +391,9 @@ export default class InspectorProxy implements InspectorProxyQueries {
// terminate() emits 'close' immediately, allowing us to handle it and
// inform any clients.
socket.terminate();
this.#logger?.error(
`Connection terminated with DevTools after not responding for ${MAX_PONG_LATENCY_MS / 1000} seconds.`,
);
}, MAX_PONG_LATENCY_MS).unref();
});
}, intervalMs).unref();
Expand All @@ -376,7 +407,12 @@ export default class InspectorProxy implements InspectorProxyQueries {
socket.on('pong', onAnyMessageFromDebugger);
socket.on('message', onAnyMessageFromDebugger);

socket.on('close', () => {
socket.on('close', (code: number, reason: string) => {
this.#logger?.info(
"Connection to DevTools closed with code '%s' and reason '%s'.",
String(code),
reason,
);
shouldSetTerminateTimeout = false;
terminateTimeout && clearTimeout(terminateTimeout);
clearTimeout(pingTimeout);
Expand Down