This commit improves the URL health checking mechanism to reduce false negatives. - Treat all 2XX status codes as successful, addressing issues with codes like `204`. - Improve URL matching to exclude URLs within Markdown inline code block and support URLs containing parentheses. - Add `forceHttpGetForUrlPatterns` to customize HTTP method per URL to allow verifying URLs behind CDN/WAFs that do not respond to HTTP HEAD. - Send the Host header for improved handling of webpages behind proxies. - Improve formatting and context for output messages. - Fix the defaulting options for redirects and cookie handling. - Update the user agent pool to modern browsers and platforms. - Add support for randomizing TLS fingerprint to mimic various clients better, improving the effectiveness of checks. However, this is not fully supported by Node.js's HTTP client; see nodejs/undici#1983 for more details. - Use `AbortSignal` instead of `AbortController` as more modern and simpler way to handle timeouts.
119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import { filterEmpty } from '@tests/shared/Text';
|
|
import { runCommand } from '../../utils/run-command';
|
|
import { log, LogLevel } from '../../utils/log';
|
|
import { SupportedPlatform, CURRENT_PLATFORM } from '../../utils/platform';
|
|
|
|
export async function captureWindowTitles(processId: number) {
|
|
if (!processId) { throw new Error('Missing process ID.'); }
|
|
|
|
const captureFunction = windowTitleCaptureFunctions[CURRENT_PLATFORM];
|
|
if (!captureFunction) {
|
|
log(`Cannot capture window title, unsupported OS: ${SupportedPlatform[CURRENT_PLATFORM]}`, LogLevel.Warn);
|
|
return undefined;
|
|
}
|
|
|
|
return captureFunction(processId);
|
|
}
|
|
|
|
const windowTitleCaptureFunctions: {
|
|
readonly [K in SupportedPlatform]: (processId: number) => Promise<string[]>;
|
|
} = {
|
|
[SupportedPlatform.macOS]: (processId) => captureTitlesOnMac(processId),
|
|
[SupportedPlatform.Linux]: (processId) => captureTitlesOnLinux(processId),
|
|
[SupportedPlatform.Windows]: (processId) => captureTitlesOnWindows(processId),
|
|
};
|
|
|
|
async function captureTitlesOnWindows(processId: number): Promise<string[]> {
|
|
if (!processId) { throw new Error('Missing process ID.'); }
|
|
|
|
const { stdout: tasklistOutput, error } = await runCommand(
|
|
`tasklist /FI "PID eq ${processId}" /fo list /v`,
|
|
);
|
|
if (error) {
|
|
log(`Failed to retrieve window title.\n${error}`, LogLevel.Warn);
|
|
return [];
|
|
}
|
|
const regex = /Window Title:\s*(.*)/;
|
|
const match = regex.exec(tasklistOutput);
|
|
if (match && match.length > 1 && match[1]) {
|
|
const title = match[1].trim();
|
|
if (title === 'N/A') {
|
|
return [];
|
|
}
|
|
return [title];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
async function captureTitlesOnLinux(processId: number): Promise<string[]> {
|
|
if (!processId) { throw new Error('Missing process ID.'); }
|
|
|
|
const { stdout: windowIdsOutput, error: windowIdError } = await runCommand(
|
|
`xdotool search --pid '${processId}'`,
|
|
);
|
|
|
|
if (windowIdError || !windowIdsOutput) {
|
|
return [];
|
|
}
|
|
|
|
const windowIds = windowIdsOutput.trim().split('\n');
|
|
|
|
const titles = await Promise.all(windowIds.map(async (windowId) => {
|
|
const { stdout: titleOutput, error: titleError } = await runCommand(
|
|
`xprop -id ${windowId} | grep "WM_NAME(STRING)" | cut -d '=' -f 2 | sed 's/^[[:space:]]*"\\(.*\\)"[[:space:]]*$/\\1/'`,
|
|
);
|
|
if (titleError || !titleOutput) {
|
|
return undefined;
|
|
}
|
|
return titleOutput.trim();
|
|
}));
|
|
|
|
return filterEmpty(titles);
|
|
}
|
|
|
|
let hasAssistiveAccessOnMac = true;
|
|
|
|
async function captureTitlesOnMac(processId: number): Promise<string[]> {
|
|
if (!processId) { throw new Error('Missing process ID.'); }
|
|
if (!hasAssistiveAccessOnMac) {
|
|
return [];
|
|
}
|
|
const script = `
|
|
tell application "System Events"
|
|
try
|
|
set targetProcess to first process whose unix id is ${processId}
|
|
on error
|
|
return
|
|
end try
|
|
tell targetProcess
|
|
set allWindowNames to {}
|
|
repeat with aWindow in windows
|
|
set end of allWindowNames to name of aWindow
|
|
end repeat
|
|
return allWindowNames
|
|
end tell
|
|
end tell
|
|
`;
|
|
const argument = script.trim()
|
|
.split(/[\r\n]+/)
|
|
.map((line) => `-e '${line.trim()}'`)
|
|
.join(' ');
|
|
|
|
const { stdout: titleOutput, error } = await runCommand(`osascript ${argument}`);
|
|
if (error) {
|
|
let errorMessage = '';
|
|
if (error.includes('-25211')) {
|
|
errorMessage += 'Capturing window title requires assistive access. You do not have it.\n';
|
|
hasAssistiveAccessOnMac = false;
|
|
}
|
|
errorMessage += error;
|
|
log(errorMessage, LogLevel.Warn);
|
|
return [];
|
|
}
|
|
const title = titleOutput?.trim();
|
|
if (!title) {
|
|
return [];
|
|
}
|
|
return [title];
|
|
}
|