fix: route web-probe through YAML proxy
This commit is contained in:
@@ -6727,9 +6727,10 @@ function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, unknown>
|
|||||||
if (options.action === "script") return runNodeWebProbeScript(options, spec, secretSpec, material, credential);
|
if (options.action === "script") return runNodeWebProbeScript(options, spec, secretSpec, material, credential);
|
||||||
if (options.commandTimeoutSeconds > 55) return runNodeWebProbeAsync(options, spec, secretSpec, material, credential);
|
if (options.commandTimeoutSeconds > 55) return runNodeWebProbeAsync(options, spec, secretSpec, material, credential);
|
||||||
const probeArgs = nodeWebProbeRunArgs(options, "run");
|
const probeArgs = nodeWebProbeRunArgs(options, "run");
|
||||||
|
const webProbeProxy = nodeWebProbeHostProxyEnv(spec);
|
||||||
const script = [
|
const script = [
|
||||||
"set -eu",
|
"set -eu",
|
||||||
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)} HWLAB_WEB_PASS=${shellQuote(material.password)} ${probeArgs.map(shellQuote).join(" ")}`,
|
`${[...webProbeProxy.envAssignments, `HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`, `HWLAB_WEB_PASS=${shellQuote(material.password)}`].join(" ")} ${probeArgs.map(shellQuote).join(" ")}`,
|
||||||
].join("\n");
|
].join("\n");
|
||||||
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
||||||
const probe = compactWebProbeResult(parseJsonObject(result.stdout));
|
const probe = compactWebProbeResult(parseJsonObject(result.stdout));
|
||||||
@@ -6748,6 +6749,7 @@ function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, unknown>
|
|||||||
lane: options.lane,
|
lane: options.lane,
|
||||||
workspace: spec.workspace,
|
workspace: spec.workspace,
|
||||||
url: options.url,
|
url: options.url,
|
||||||
|
network: webProbeProxy.summary,
|
||||||
credential,
|
credential,
|
||||||
commandTimeout: {
|
commandTimeout: {
|
||||||
seconds: options.commandTimeoutSeconds,
|
seconds: options.commandTimeoutSeconds,
|
||||||
@@ -6791,9 +6793,10 @@ function runNodeWebProbeAsync(
|
|||||||
credential: Record<string, unknown>,
|
credential: Record<string, unknown>,
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
const startArgs = nodeWebProbeRunArgs(options, "start");
|
const startArgs = nodeWebProbeRunArgs(options, "start");
|
||||||
|
const webProbeProxy = nodeWebProbeHostProxyEnv(spec);
|
||||||
const startScript = [
|
const startScript = [
|
||||||
"set -eu",
|
"set -eu",
|
||||||
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)} HWLAB_WEB_PASS=${shellQuote(material.password ?? "")} ${startArgs.map(shellQuote).join(" ")}`,
|
`${[...webProbeProxy.envAssignments, `HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`, `HWLAB_WEB_PASS=${shellQuote(material.password ?? "")}`].join(" ")} ${startArgs.map(shellQuote).join(" ")}`,
|
||||||
].join("\n");
|
].join("\n");
|
||||||
const startResult = runTransWorkspaceStdinScript(options.node, spec.workspace, startScript, 55);
|
const startResult = runTransWorkspaceStdinScript(options.node, spec.workspace, startScript, 55);
|
||||||
const start = parseJsonObject(startResult.stdout);
|
const start = parseJsonObject(startResult.stdout);
|
||||||
@@ -6807,6 +6810,7 @@ function runNodeWebProbeAsync(
|
|||||||
lane: options.lane,
|
lane: options.lane,
|
||||||
workspace: spec.workspace,
|
workspace: spec.workspace,
|
||||||
url: options.url,
|
url: options.url,
|
||||||
|
network: webProbeProxy.summary,
|
||||||
credential,
|
credential,
|
||||||
mode: "async-start",
|
mode: "async-start",
|
||||||
commandTimeout: webProbeCommandTimeoutSummary(options, false),
|
commandTimeout: webProbeCommandTimeoutSummary(options, false),
|
||||||
@@ -6841,6 +6845,7 @@ function runNodeWebProbeAsync(
|
|||||||
lane: options.lane,
|
lane: options.lane,
|
||||||
workspace: spec.workspace,
|
workspace: spec.workspace,
|
||||||
url: options.url,
|
url: options.url,
|
||||||
|
network: webProbeProxy.summary,
|
||||||
credential,
|
credential,
|
||||||
mode: "async",
|
mode: "async",
|
||||||
commandTimeout: webProbeCommandTimeoutSummary(options, poll.timedOut),
|
commandTimeout: webProbeCommandTimeoutSummary(options, poll.timedOut),
|
||||||
@@ -6947,6 +6952,116 @@ function webProbeCredential(secretSpec: RuntimeSecretSpec, material: BootstrapAd
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NodeWebProbeHostProxyEnv {
|
||||||
|
readonly envAssignments: string[];
|
||||||
|
readonly summary: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeWebProbeHostProxyEnv(spec: HwlabRuntimeLaneSpec): NodeWebProbeHostProxyEnv {
|
||||||
|
const proxy = spec.networkProfile.proxy;
|
||||||
|
const serviceCache = new Map<string, string>();
|
||||||
|
const http = resolveNodeWebProbeHostProxyUrl(spec, proxy.http, serviceCache);
|
||||||
|
const https = resolveNodeWebProbeHostProxyUrl(spec, proxy.https, serviceCache);
|
||||||
|
const all = resolveNodeWebProbeHostProxyUrl(spec, proxy.all, serviceCache);
|
||||||
|
const noProxy = proxy.noProxy.join(",");
|
||||||
|
return {
|
||||||
|
envAssignments: [
|
||||||
|
["HTTP_PROXY", http.url],
|
||||||
|
["HTTPS_PROXY", https.url],
|
||||||
|
["ALL_PROXY", all.url],
|
||||||
|
["http_proxy", http.url],
|
||||||
|
["https_proxy", https.url],
|
||||||
|
["all_proxy", all.url],
|
||||||
|
["NO_PROXY", noProxy],
|
||||||
|
["no_proxy", noProxy],
|
||||||
|
].map(([key, value]) => `${key}=${shellQuote(value)}`),
|
||||||
|
summary: {
|
||||||
|
source: "yaml",
|
||||||
|
mode: "host-env",
|
||||||
|
networkProfileId: spec.networkProfileId,
|
||||||
|
proxy: {
|
||||||
|
http: http.summary,
|
||||||
|
https: https.summary,
|
||||||
|
all: all.summary,
|
||||||
|
noProxyCount: proxy.noProxy.length,
|
||||||
|
},
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNodeWebProbeHostProxyUrl(
|
||||||
|
spec: HwlabRuntimeLaneSpec,
|
||||||
|
rawUrl: string,
|
||||||
|
serviceCache: Map<string, string>,
|
||||||
|
): { url: string; summary: Record<string, unknown> } {
|
||||||
|
let parsed: URL;
|
||||||
|
try {
|
||||||
|
parsed = new URL(rawUrl);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`config/hwlab-node-lanes.yaml networkProfiles.${spec.networkProfileId}.proxy contains invalid proxy URL: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
const service = parseKubernetesServiceDnsHost(parsed.hostname);
|
||||||
|
if (service === null) {
|
||||||
|
return {
|
||||||
|
url: rawUrl,
|
||||||
|
summary: {
|
||||||
|
mode: "host-url",
|
||||||
|
host: parsed.hostname,
|
||||||
|
port: parsed.port || null,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const clusterIp = resolveKubernetesServiceClusterIp(spec, service.namespace, service.name, serviceCache);
|
||||||
|
const originalHost = parsed.hostname;
|
||||||
|
parsed.hostname = clusterIp;
|
||||||
|
const resolvedUrl = normalizedProxyUrl(parsed);
|
||||||
|
return {
|
||||||
|
url: resolvedUrl,
|
||||||
|
summary: {
|
||||||
|
mode: "k8s-service-cluster-ip",
|
||||||
|
service: service.name,
|
||||||
|
namespace: service.namespace,
|
||||||
|
originalHost,
|
||||||
|
resolvedHost: clusterIp,
|
||||||
|
port: parsed.port || null,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseKubernetesServiceDnsHost(hostname: string): { name: string; namespace: string } | null {
|
||||||
|
const match = hostname.toLowerCase().match(/^([a-z0-9]([-a-z0-9]*[a-z0-9])?)\.([a-z0-9]([-a-z0-9]*[a-z0-9])?)\.svc(?:\.cluster\.local)?$/u);
|
||||||
|
if (match === null) return null;
|
||||||
|
return { name: match[1] ?? "", namespace: match[3] ?? "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveKubernetesServiceClusterIp(
|
||||||
|
spec: HwlabRuntimeLaneSpec,
|
||||||
|
namespace: string,
|
||||||
|
serviceName: string,
|
||||||
|
serviceCache: Map<string, string>,
|
||||||
|
): string {
|
||||||
|
const cacheKey = `${namespace}/${serviceName}`;
|
||||||
|
const cached = serviceCache.get(cacheKey);
|
||||||
|
if (cached !== undefined) return cached;
|
||||||
|
const result = runCommand([transPath(), spec.nodeKubeRoute, "get", "svc", "-n", namespace, serviceName, "-o", "jsonpath={.spec.clusterIP}"], repoRoot, { timeoutMs: 20_000 });
|
||||||
|
const clusterIp = result.stdout.trim();
|
||||||
|
if (result.exitCode !== 0 || clusterIp.length === 0) {
|
||||||
|
const reason = result.stderr.trim().slice(-500) || result.stdout.trim().slice(-500) || `exitCode=${result.exitCode}`;
|
||||||
|
throw new Error(`web-probe proxy service resolution failed for ${spec.nodeId}/${spec.lane} ${namespace}/${serviceName}: ${reason}`);
|
||||||
|
}
|
||||||
|
serviceCache.set(cacheKey, clusterIp);
|
||||||
|
return clusterIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedProxyUrl(parsed: URL): string {
|
||||||
|
const value = parsed.toString();
|
||||||
|
if (parsed.pathname === "/" && parsed.search === "" && parsed.hash === "") return value.replace(/\/$/u, "");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function runNodeWebProbeObserve(
|
function runNodeWebProbeObserve(
|
||||||
options: NodeWebProbeObserveOptions,
|
options: NodeWebProbeObserveOptions,
|
||||||
spec: HwlabRuntimeLaneSpec,
|
spec: HwlabRuntimeLaneSpec,
|
||||||
@@ -6977,6 +7092,7 @@ function runNodeWebProbeObserveStart(
|
|||||||
const day = timestamp.slice(0, 8);
|
const day = timestamp.slice(0, 8);
|
||||||
const stateDir = `.state/web-observe/${safeWebObserveSegment(options.node)}/${safeWebObserveSegment(options.lane)}/${day.slice(0, 4)}/${day.slice(4, 6)}/${day.slice(6, 8)}/${timestamp}_${safeWebObserveTargetSegment(options.targetPath)}_${jobId}`;
|
const stateDir = `.state/web-observe/${safeWebObserveSegment(options.node)}/${safeWebObserveSegment(options.lane)}/${day.slice(0, 4)}/${day.slice(4, 6)}/${day.slice(6, 8)}/${timestamp}_${safeWebObserveTargetSegment(options.targetPath)}_${jobId}`;
|
||||||
const runnerB64 = Buffer.from(nodeWebObserveRunnerSource(), "utf8").toString("base64");
|
const runnerB64 = Buffer.from(nodeWebObserveRunnerSource(), "utf8").toString("base64");
|
||||||
|
const webProbeProxy = nodeWebProbeHostProxyEnv(spec);
|
||||||
const script = [
|
const script = [
|
||||||
"set -eu",
|
"set -eu",
|
||||||
`state_dir=${shellQuote(stateDir)}`,
|
`state_dir=${shellQuote(stateDir)}`,
|
||||||
@@ -6986,6 +7102,7 @@ function runNodeWebProbeObserveStart(
|
|||||||
`node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$runner" ${shellQuote(runnerB64)}`,
|
`node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$runner" ${shellQuote(runnerB64)}`,
|
||||||
"chmod 700 \"$runner\"",
|
"chmod 700 \"$runner\"",
|
||||||
[
|
[
|
||||||
|
...webProbeProxy.envAssignments,
|
||||||
`HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
|
`HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
|
||||||
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`,
|
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`,
|
||||||
`HWLAB_WEB_PASS=${shellQuote(material.password)}`,
|
`HWLAB_WEB_PASS=${shellQuote(material.password)}`,
|
||||||
@@ -7029,6 +7146,7 @@ function runNodeWebProbeObserveStart(
|
|||||||
lane: options.lane,
|
lane: options.lane,
|
||||||
workspace: spec.workspace,
|
workspace: spec.workspace,
|
||||||
url: options.url,
|
url: options.url,
|
||||||
|
network: webProbeProxy.summary,
|
||||||
targetPath: options.targetPath,
|
targetPath: options.targetPath,
|
||||||
id: observerId,
|
id: observerId,
|
||||||
credential,
|
credential,
|
||||||
@@ -8172,7 +8290,8 @@ function runNodeWebProbeScript(
|
|||||||
material: BootstrapAdminPasswordMaterial,
|
material: BootstrapAdminPasswordMaterial,
|
||||||
credential: Record<string, unknown>,
|
credential: Record<string, unknown>,
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
const script = nodeWebProbeScriptRemoteShell(options, secretSpec, material.password ?? "");
|
const webProbeProxy = nodeWebProbeHostProxyEnv(spec);
|
||||||
|
const script = nodeWebProbeScriptRemoteShell(options, secretSpec, material.password ?? "", webProbeProxy);
|
||||||
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
||||||
const stdoutReport = parseJsonObject(result.stdout);
|
const stdoutReport = parseJsonObject(result.stdout);
|
||||||
const runPaths = webProbeScriptRunPathsFromStderr(result.stderr);
|
const runPaths = webProbeScriptRunPathsFromStderr(result.stderr);
|
||||||
@@ -8250,6 +8369,7 @@ function runNodeWebProbeScript(
|
|||||||
lane: options.lane,
|
lane: options.lane,
|
||||||
workspace: spec.workspace,
|
workspace: spec.workspace,
|
||||||
url: options.url,
|
url: options.url,
|
||||||
|
network: webProbeProxy.summary,
|
||||||
credential,
|
credential,
|
||||||
scriptSource: options.scriptSource,
|
scriptSource: options.scriptSource,
|
||||||
degradedReason,
|
degradedReason,
|
||||||
@@ -8274,7 +8394,7 @@ function runNodeWebProbeScript(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeWebProbeScriptRemoteShell(options: NodeWebProbeScriptOptions, secretSpec: RuntimeSecretSpec, password: string): string {
|
function nodeWebProbeScriptRemoteShell(options: NodeWebProbeScriptOptions, secretSpec: RuntimeSecretSpec, password: string, webProbeProxy: NodeWebProbeHostProxyEnv): string {
|
||||||
const userScriptB64 = Buffer.from(options.scriptText, "utf8").toString("base64");
|
const userScriptB64 = Buffer.from(options.scriptText, "utf8").toString("base64");
|
||||||
const runnerB64 = Buffer.from(nodeWebProbeScriptRunnerSource(), "utf8").toString("base64");
|
const runnerB64 = Buffer.from(nodeWebProbeScriptRunnerSource(), "utf8").toString("base64");
|
||||||
return [
|
return [
|
||||||
@@ -8291,6 +8411,7 @@ function nodeWebProbeScriptRemoteShell(options: NodeWebProbeScriptOptions, secre
|
|||||||
`node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$user_script" ${shellQuote(userScriptB64)}`,
|
`node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$user_script" ${shellQuote(userScriptB64)}`,
|
||||||
`node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$runner" ${shellQuote(runnerB64)}`,
|
`node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$runner" ${shellQuote(runnerB64)}`,
|
||||||
[
|
[
|
||||||
|
...webProbeProxy.envAssignments,
|
||||||
`HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
|
`HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
|
||||||
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`,
|
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`,
|
||||||
`HWLAB_WEB_PASS=${shellQuote(password)}`,
|
`HWLAB_WEB_PASS=${shellQuote(password)}`,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const sampleIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SAMPLE_
|
|||||||
const screenshotIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS, 300000);
|
const screenshotIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS, 300000);
|
||||||
const maxSamples = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SAMPLES, 0);
|
const maxSamples = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SAMPLES, 0);
|
||||||
const viewport = parseViewport(process.env.UNIDESK_WEB_OBSERVE_VIEWPORT || "1440x900");
|
const viewport = parseViewport(process.env.UNIDESK_WEB_OBSERVE_VIEWPORT || "1440x900");
|
||||||
|
const playwrightProxy = proxyConfigFromEnv(baseUrl);
|
||||||
const pageId = "page-" + randomBytes(4).toString("hex");
|
const pageId = "page-" + randomBytes(4).toString("hex");
|
||||||
const dirs = {
|
const dirs = {
|
||||||
commandsPending: path.join(stateDir, "commands", "pending"),
|
commandsPending: path.join(stateDir, "commands", "pending"),
|
||||||
@@ -63,7 +64,7 @@ try {
|
|||||||
const launcher = await import(pathToFileURL(path.resolve("scripts/src/browser-launcher.mjs")).href);
|
const launcher = await import(pathToFileURL(path.resolve("scripts/src/browser-launcher.mjs")).href);
|
||||||
const { chromium } = await launcher.importPlaywright();
|
const { chromium } = await launcher.importPlaywright();
|
||||||
browser = await launcher.launchChromium(chromium);
|
browser = await launcher.launchChromium(chromium);
|
||||||
context = await browser.newContext({ viewport });
|
context = await browser.newContext({ viewport, ...(playwrightProxy === null ? {} : { proxy: playwrightProxy }) });
|
||||||
auth = await runControlCommand({ id: "startup-login", type: "login", createdAt: startedAt, source: "startup" }, async () => authenticate(context));
|
auth = await runControlCommand({ id: "startup-login", type: "login", createdAt: startedAt, source: "startup" }, async () => authenticate(context));
|
||||||
page = await context.newPage();
|
page = await context.newPage();
|
||||||
attachPassiveListeners(page);
|
attachPassiveListeners(page);
|
||||||
@@ -109,6 +110,7 @@ async function writeManifest(extra = {}) {
|
|||||||
stateDir,
|
stateDir,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
targetPath,
|
targetPath,
|
||||||
|
network: publicNetwork(playwrightProxy),
|
||||||
pageAuthority: { browser: "chromium", context: "single", pageId, continuityBreaksRecorded: true },
|
pageAuthority: { browser: "chromium", context: "single", pageId, continuityBreaksRecorded: true },
|
||||||
pageProvenance: compactPageProvenance(currentPageProvenance),
|
pageProvenance: compactPageProvenance(currentPageProvenance),
|
||||||
sampling: { mode: "passive", sampleIntervalMs, screenshotIntervalMs, maxSamples, observerInitiatedDefault: false, responseBodyReadDefault: false },
|
sampling: { mode: "passive", sampleIntervalMs, screenshotIntervalMs, maxSamples, observerInitiatedDefault: false, responseBodyReadDefault: false },
|
||||||
@@ -283,6 +285,62 @@ function publicAuth(value) {
|
|||||||
return { ok: value.ok === true, method: value.method, status: value.status, cookiePresent: value.cookiePresent === true, cookieNames: value.cookieNames || [], valuesRedacted: true };
|
return { ok: value.ok === true, method: value.method, status: value.status, cookiePresent: value.cookiePresent === true, cookieNames: value.cookieNames || [], valuesRedacted: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function proxyConfigFromEnv(targetBaseUrl) {
|
||||||
|
let target;
|
||||||
|
try {
|
||||||
|
target = new URL(targetBaseUrl);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy || "";
|
||||||
|
if (noProxyMatches(target.hostname, noProxy)) return null;
|
||||||
|
const raw = target.protocol === "https:"
|
||||||
|
? process.env.HTTPS_PROXY || process.env.https_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTP_PROXY || process.env.http_proxy || ""
|
||||||
|
: process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTPS_PROXY || process.env.https_proxy || "";
|
||||||
|
if (!raw) return null;
|
||||||
|
return { server: raw };
|
||||||
|
}
|
||||||
|
|
||||||
|
function noProxyMatches(hostname, rawList) {
|
||||||
|
const host = String(hostname || "").toLowerCase();
|
||||||
|
if (!host) return false;
|
||||||
|
return String(rawList || "").split(",").some((raw) => {
|
||||||
|
let item = raw.trim().toLowerCase();
|
||||||
|
if (!item) return false;
|
||||||
|
if (item === "*") return true;
|
||||||
|
item = item.replace(/^\*\./u, ".");
|
||||||
|
const portIndex = item.lastIndexOf(":");
|
||||||
|
if (portIndex > -1 && !item.includes("]")) item = item.slice(0, portIndex);
|
||||||
|
if (item.startsWith(".")) return host === item.slice(1) || host.endsWith(item);
|
||||||
|
return host === item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicNetwork(proxy) {
|
||||||
|
return {
|
||||||
|
proxy: proxy === null ? { enabled: false, source: "env", valuesRedacted: true } : {
|
||||||
|
enabled: true,
|
||||||
|
source: "env",
|
||||||
|
server: publicProxyServer(proxy.server),
|
||||||
|
valuesRedacted: true,
|
||||||
|
},
|
||||||
|
valuesRedacted: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicProxyServer(raw) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(String(raw || ""));
|
||||||
|
parsed.username = "";
|
||||||
|
parsed.password = "";
|
||||||
|
const value = parsed.toString();
|
||||||
|
if (parsed.pathname === "/" && parsed.search === "" && parsed.hash === "") return value.replace(/\/$/u, "");
|
||||||
|
return value;
|
||||||
|
} catch {
|
||||||
|
return String(raw || "").replace(/\/\/[^/@]+@/u, "//[redacted]@");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function gotoTarget(rawTarget) {
|
async function gotoTarget(rawTarget) {
|
||||||
const target = new URL(String(rawTarget || targetPath), baseUrl).toString();
|
const target = new URL(String(rawTarget || targetPath), baseUrl).toString();
|
||||||
const beforeUrl = currentPageUrl();
|
const beforeUrl = currentPageUrl();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const runDir = path.resolve(process.env.UNIDESK_WEB_PROBE_RUN_DIR || ".state/web
|
|||||||
const userScript = path.resolve(process.env.UNIDESK_WEB_PROBE_USER_SCRIPT || path.join(runDir, "user-script.mjs"));
|
const userScript = path.resolve(process.env.UNIDESK_WEB_PROBE_USER_SCRIPT || path.join(runDir, "user-script.mjs"));
|
||||||
const timeoutMs = positiveInteger(process.env.UNIDESK_WEB_PROBE_TIMEOUT_MS, 30000);
|
const timeoutMs = positiveInteger(process.env.UNIDESK_WEB_PROBE_TIMEOUT_MS, 30000);
|
||||||
const viewport = parseViewport(process.env.UNIDESK_WEB_PROBE_VIEWPORT || "1440x900");
|
const viewport = parseViewport(process.env.UNIDESK_WEB_PROBE_VIEWPORT || "1440x900");
|
||||||
|
const playwrightProxy = proxyConfigFromEnv(baseUrl);
|
||||||
const artifactRecords = [];
|
const artifactRecords = [];
|
||||||
const readinessRecords = [];
|
const readinessRecords = [];
|
||||||
const stepRecords = [];
|
const stepRecords = [];
|
||||||
@@ -34,7 +35,7 @@ try {
|
|||||||
const launcher = await import(pathToFileURL(path.resolve("scripts/src/browser-launcher.mjs")).href);
|
const launcher = await import(pathToFileURL(path.resolve("scripts/src/browser-launcher.mjs")).href);
|
||||||
const { chromium } = await launcher.importPlaywright();
|
const { chromium } = await launcher.importPlaywright();
|
||||||
browser = await launcher.launchChromium(chromium);
|
browser = await launcher.launchChromium(chromium);
|
||||||
context = await browser.newContext({ viewport });
|
context = await browser.newContext({ viewport, ...(playwrightProxy === null ? {} : { proxy: playwrightProxy }) });
|
||||||
auth = await authenticate(context);
|
auth = await authenticate(context);
|
||||||
if (!auth.ok) throw new Error("auth-login-failed");
|
if (!auth.ok) throw new Error("auth-login-failed");
|
||||||
page = await context.newPage();
|
page = await context.newPage();
|
||||||
@@ -55,6 +56,7 @@ try {
|
|||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
startedAt,
|
startedAt,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
|
network: publicNetwork(playwrightProxy),
|
||||||
finalUrl: lastUrl,
|
finalUrl: lastUrl,
|
||||||
lastUrl,
|
lastUrl,
|
||||||
scriptSha256: userScriptSha256,
|
scriptSha256: userScriptSha256,
|
||||||
@@ -83,6 +85,7 @@ try {
|
|||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
startedAt,
|
startedAt,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
|
network: publicNetwork(playwrightProxy),
|
||||||
finalUrl: lastUrl,
|
finalUrl: lastUrl,
|
||||||
lastUrl,
|
lastUrl,
|
||||||
scriptSha256: userScriptSha256,
|
scriptSha256: userScriptSha256,
|
||||||
@@ -365,6 +368,62 @@ function scriptHelpers() {
|
|||||||
return helpers;
|
return helpers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function proxyConfigFromEnv(targetBaseUrl) {
|
||||||
|
let target;
|
||||||
|
try {
|
||||||
|
target = new URL(targetBaseUrl);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const noProxy = process.env.NO_PROXY || process.env.no_proxy || "";
|
||||||
|
if (noProxyMatches(target.hostname, noProxy)) return null;
|
||||||
|
const raw = target.protocol === "https:"
|
||||||
|
? process.env.HTTPS_PROXY || process.env.https_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTP_PROXY || process.env.http_proxy || ""
|
||||||
|
: process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTPS_PROXY || process.env.https_proxy || "";
|
||||||
|
if (!raw) return null;
|
||||||
|
return { server: raw };
|
||||||
|
}
|
||||||
|
|
||||||
|
function noProxyMatches(hostname, rawList) {
|
||||||
|
const host = String(hostname || "").toLowerCase();
|
||||||
|
if (!host) return false;
|
||||||
|
return String(rawList || "").split(",").some((raw) => {
|
||||||
|
let item = raw.trim().toLowerCase();
|
||||||
|
if (!item) return false;
|
||||||
|
if (item === "*") return true;
|
||||||
|
item = item.replace(/^\*\./u, ".");
|
||||||
|
const portIndex = item.lastIndexOf(":");
|
||||||
|
if (portIndex > -1 && !item.includes("]")) item = item.slice(0, portIndex);
|
||||||
|
if (item.startsWith(".")) return host === item.slice(1) || host.endsWith(item);
|
||||||
|
return host === item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicNetwork(proxy) {
|
||||||
|
return {
|
||||||
|
proxy: proxy === null ? { enabled: false, source: "env", valuesRedacted: true } : {
|
||||||
|
enabled: true,
|
||||||
|
source: "env",
|
||||||
|
server: publicProxyServer(proxy.server),
|
||||||
|
valuesRedacted: true,
|
||||||
|
},
|
||||||
|
valuesRedacted: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicProxyServer(raw) {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(String(raw || ""));
|
||||||
|
parsed.username = "";
|
||||||
|
parsed.password = "";
|
||||||
|
const value = parsed.toString();
|
||||||
|
if (parsed.pathname === "/" && parsed.search === "" && parsed.hash === "") return value.replace(/\/$/u, "");
|
||||||
|
return value;
|
||||||
|
} catch {
|
||||||
|
return String(raw || "").replace(/\/\/[^/@]+@/u, "//[redacted]@");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createLivePageProxy() {
|
function createLivePageProxy() {
|
||||||
return new Proxy({}, {
|
return new Proxy({}, {
|
||||||
get(_target, prop) {
|
get(_target, prop) {
|
||||||
|
|||||||
Reference in New Issue
Block a user