mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
232 lines
5.7 KiB
JavaScript
232 lines
5.7 KiB
JavaScript
const fs = require('fs');
|
|
const http = require('http');
|
|
const https = require('https');
|
|
const path = require('path');
|
|
const util = require('util');
|
|
|
|
const ALLOWED_WAIT_UNTIL = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']);
|
|
|
|
function normalizeTimeout(value, fallback) {
|
|
const parsed = Number(value);
|
|
if (Number.isFinite(parsed) && parsed > 0) {
|
|
return Math.round(parsed);
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function isPlainObject(value) {
|
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
}
|
|
|
|
function hasOwnProperty(value, key) {
|
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
}
|
|
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
function writeStream(stream, text) {
|
|
return new Promise((resolve, reject) => {
|
|
stream.write(text, (error) => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
async function closeBrowserConnection(browser) {
|
|
if (!browser || typeof browser.close !== 'function') {
|
|
return;
|
|
}
|
|
await browser.close({ reason: 'automation task finished' }).catch(() => {});
|
|
}
|
|
|
|
function normalizeEndpointCandidate(value) {
|
|
const normalized = String(value || '').trim();
|
|
if (!normalized) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
const parsed = new URL(normalized);
|
|
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
|
|
return '';
|
|
}
|
|
if (parsed.port === '0') {
|
|
return '';
|
|
}
|
|
if ((parsed.protocol === 'http:' || parsed.protocol === 'https:') && (!parsed.pathname || parsed.pathname === '/') && !parsed.search && !parsed.hash) {
|
|
return parsed.origin;
|
|
}
|
|
return parsed.toString();
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function buildConnectEndpoints(payload, session) {
|
|
const candidates = [];
|
|
const seen = new Set();
|
|
|
|
const pushCandidate = (value) => {
|
|
const endpoint = normalizeEndpointCandidate(value);
|
|
if (!endpoint || seen.has(endpoint)) {
|
|
return;
|
|
}
|
|
seen.add(endpoint);
|
|
candidates.push(endpoint);
|
|
};
|
|
|
|
pushCandidate(session && session.cdpUrl);
|
|
|
|
const debugPort = Number(session && session.debugPort);
|
|
if (Number.isFinite(debugPort) && debugPort > 0) {
|
|
pushCandidate(`http://127.0.0.1:${Math.round(debugPort)}`);
|
|
}
|
|
|
|
pushCandidate(payload && payload.launchBaseUrl);
|
|
return candidates;
|
|
}
|
|
|
|
function normalizePathUnderRoot(rootDir, targetName) {
|
|
const normalizedName = String(targetName || '').trim();
|
|
const resolvedRoot = path.resolve(String(rootDir || ''));
|
|
if (!resolvedRoot) {
|
|
throw new Error('artifactDir is required');
|
|
}
|
|
|
|
const candidate = normalizedName ? path.resolve(resolvedRoot, normalizedName) : resolvedRoot;
|
|
if (candidate !== resolvedRoot && !candidate.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
throw new Error('artifact path escapes root directory');
|
|
}
|
|
return candidate;
|
|
}
|
|
|
|
async function requestJSON(method, requestURL, body, headers = {}) {
|
|
const target = new URL(requestURL);
|
|
const transport = target.protocol === 'https:' ? https : http;
|
|
const payload = body == null ? '' : JSON.stringify(body);
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
const req = transport.request(
|
|
{
|
|
protocol: target.protocol,
|
|
hostname: target.hostname,
|
|
port: target.port,
|
|
path: `${target.pathname}${target.search}`,
|
|
method,
|
|
headers: {
|
|
Accept: 'application/json',
|
|
...(payload
|
|
? {
|
|
'Content-Type': 'application/json',
|
|
'Content-Length': Buffer.byteLength(payload),
|
|
}
|
|
: {}),
|
|
...headers,
|
|
},
|
|
},
|
|
(res) => {
|
|
const chunks = [];
|
|
res.on('data', (chunk) => chunks.push(chunk));
|
|
res.on('end', () => {
|
|
const rawText = Buffer.concat(chunks).toString('utf8').trim();
|
|
let responseBody = {};
|
|
if (rawText) {
|
|
try {
|
|
responseBody = JSON.parse(rawText);
|
|
} catch {
|
|
responseBody = { rawBody: rawText };
|
|
}
|
|
}
|
|
resolve({
|
|
status: res.statusCode || 0,
|
|
body: responseBody,
|
|
});
|
|
});
|
|
}
|
|
);
|
|
|
|
req.on('error', reject);
|
|
if (payload) {
|
|
req.write(payload);
|
|
}
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function inspectValue(value) {
|
|
return util.inspect(value, {
|
|
depth: 4,
|
|
breakLength: 120,
|
|
maxArrayLength: 20,
|
|
compact: false,
|
|
});
|
|
}
|
|
|
|
function toSerializable(value, seen = new WeakSet()) {
|
|
if (value == null) {
|
|
return value;
|
|
}
|
|
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
return value;
|
|
}
|
|
if (typeof value === 'bigint') {
|
|
return value.toString();
|
|
}
|
|
if (value instanceof Date) {
|
|
return value.toISOString();
|
|
}
|
|
if (value instanceof Error) {
|
|
return {
|
|
name: value.name,
|
|
message: value.message,
|
|
stack: value.stack,
|
|
};
|
|
}
|
|
if (Buffer.isBuffer(value)) {
|
|
return value.toString('utf8');
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.map((item) => toSerializable(item, seen));
|
|
}
|
|
if (typeof value === 'function') {
|
|
return `[Function ${value.name || 'anonymous'}]`;
|
|
}
|
|
if (typeof value !== 'object') {
|
|
return inspectValue(value);
|
|
}
|
|
if (seen.has(value)) {
|
|
return '[Circular]';
|
|
}
|
|
seen.add(value);
|
|
|
|
const prototype = Object.getPrototypeOf(value);
|
|
if (prototype === Object.prototype || prototype === null) {
|
|
const result = {};
|
|
for (const [key, entry] of Object.entries(value)) {
|
|
result[key] = toSerializable(entry, seen);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return inspectValue(value);
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
normalizeTimeout,
|
|
isPlainObject,
|
|
hasOwnProperty,
|
|
sleep,
|
|
writeStream,
|
|
closeBrowserConnection,
|
|
buildConnectEndpoints,
|
|
normalizePathUnderRoot,
|
|
requestJSON,
|
|
toSerializable,
|
|
}; |