296 lines
9.6 KiB
JavaScript
296 lines
9.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import {
|
|
DISPLAY_CHROME_PATH,
|
|
DISPLAY_META_PATH,
|
|
DISPLAY_RUNTIME_HOME,
|
|
DISPLAY_PROFILE_DIR,
|
|
DISPLAY_STATE_PATH,
|
|
ensureDir,
|
|
ensureParentDir,
|
|
ensureRuntimeTree,
|
|
resolveChromePath,
|
|
resolveMetaWritePath,
|
|
resolveProfileDir,
|
|
resolveRuntimeHome,
|
|
resolveStateWritePath,
|
|
} from "./lib/runtime-paths.mjs";
|
|
import { parseArgs } from "./lib/cli-shared.mjs";
|
|
import { extractClientBin } from "./lib/auth-shared.mjs";
|
|
|
|
const DEFAULT_URL =
|
|
"https://salesintelligence-dv.oracle.com/ui/dv/ui/project.jsp?pageid=visualAnalyzer&reportmode=presentation&reportpath=%2F%40Catalog%2Fshared%2FTeam%20Sharing%20Area%2FLAD%2FLAD%20Sales%20Operations%2FLAD%20Production%2FLAD%20Knowledge%20One%20View";
|
|
const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const skillHome = path.resolve(scriptsDir, "..");
|
|
const skillsHome = path.resolve(skillHome, "..");
|
|
|
|
function usage() {
|
|
return `Refresh Oracle Sales Intelligence auth by opening Chrome.
|
|
|
|
This is an optional development and troubleshooting helper. Daily use should prefer scripts/refresh-auth-direct.mjs.
|
|
|
|
Usage:
|
|
node scripts/refresh-auth-state.mjs [options]
|
|
|
|
Options:
|
|
--runtime-home <dir> Override workspace runtime root (default: ${DISPLAY_RUNTIME_HOME})
|
|
--state-path <file> Override auth-state.json output path (default: ${DISPLAY_STATE_PATH})
|
|
--meta-path <file> Override session-meta.json output path (default: ${DISPLAY_META_PATH})
|
|
--profile-dir <dir> Override Chrome profile directory (default: ${DISPLAY_PROFILE_DIR})
|
|
--chrome-path <file> Override Chrome executable path (default: ${DISPLAY_CHROME_PATH})
|
|
--url <url> Target Oracle DV workbook URL
|
|
--timeout-ms <ms> Wait time for authenticated Oracle DV page (default: 600000)
|
|
--help Show this help
|
|
`;
|
|
}
|
|
|
|
async function pathExists(targetPath) {
|
|
try {
|
|
await fs.access(targetPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function uniquePaths(values) {
|
|
const seen = new Set();
|
|
const result = [];
|
|
for (const value of values.filter(Boolean)) {
|
|
const resolved = path.resolve(value);
|
|
if (seen.has(resolved)) continue;
|
|
seen.add(resolved);
|
|
result.push(resolved);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function bundledRuntimePackageCandidates(packageName) {
|
|
const execDir = path.dirname(process.execPath || "");
|
|
const envNodeModules = [
|
|
process.env.CODEX_NODE_MODULES,
|
|
process.env.CODEX_BUNDLED_NODE_MODULES,
|
|
process.env.NODE_PATH,
|
|
].filter(Boolean);
|
|
|
|
const packageJsonCandidates = [];
|
|
for (const nodeModulesPath of envNodeModules) {
|
|
for (const basePath of String(nodeModulesPath).split(path.delimiter)) {
|
|
if (!basePath) continue;
|
|
packageJsonCandidates.push(path.join(basePath, packageName, "package.json"));
|
|
}
|
|
}
|
|
|
|
if (process.env.CODEX_PRIMARY_RUNTIME_HOME) {
|
|
packageJsonCandidates.push(
|
|
path.join(
|
|
process.env.CODEX_PRIMARY_RUNTIME_HOME,
|
|
"dependencies",
|
|
"node",
|
|
"node_modules",
|
|
packageName,
|
|
"package.json",
|
|
),
|
|
);
|
|
}
|
|
|
|
packageJsonCandidates.push(
|
|
path.join(execDir, "node_modules", packageName, "package.json"),
|
|
path.join(execDir, "..", "node_modules", packageName, "package.json"),
|
|
path.join(execDir, "..", "..", "node_modules", packageName, "package.json"),
|
|
path.join(
|
|
os.homedir(),
|
|
".cache",
|
|
"codex-runtimes",
|
|
"codex-primary-runtime",
|
|
"dependencies",
|
|
"node",
|
|
"node_modules",
|
|
packageName,
|
|
"package.json",
|
|
),
|
|
);
|
|
|
|
return uniquePaths(packageJsonCandidates);
|
|
}
|
|
|
|
async function loadPlaywright() {
|
|
const packageNames = ["playwright", "playwright-core"];
|
|
|
|
for (const packageName of packageNames) {
|
|
try {
|
|
return {
|
|
runtime: await import(packageName),
|
|
sourceLabel: `local-module:${packageName}`,
|
|
};
|
|
} catch {}
|
|
}
|
|
|
|
const sharedSkillPackageJson = path.join(skillsHome, "playwright", "package.json");
|
|
if (await pathExists(sharedSkillPackageJson)) {
|
|
const requireFromSharedSkill = createRequire(sharedSkillPackageJson);
|
|
for (const packageName of packageNames) {
|
|
try {
|
|
return {
|
|
runtime: requireFromSharedSkill(packageName),
|
|
sourceLabel: `shared-skill:${packageName}`,
|
|
};
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
for (const packageName of packageNames) {
|
|
for (const packageJsonPath of bundledRuntimePackageCandidates(packageName)) {
|
|
if (!(await pathExists(packageJsonPath))) continue;
|
|
try {
|
|
const requireFromBundledRuntime = createRequire(packageJsonPath);
|
|
return {
|
|
runtime: requireFromBundledRuntime(packageName),
|
|
sourceLabel: `bundled-runtime:${packageJsonPath}`,
|
|
};
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
throw new Error(
|
|
[
|
|
"Could not load Playwright for the browser auth helper.",
|
|
"Tried local module resolution, ~/.codex/skills/playwright, and bundled Codex runtime locations.",
|
|
"Install a shared Playwright runtime under ~/.codex/skills/playwright or run this helper from a Codex environment with bundled node_modules.",
|
|
].join(" "),
|
|
);
|
|
}
|
|
|
|
async function ensureChromeExecutable(chromePath) {
|
|
try {
|
|
await fs.access(chromePath);
|
|
} catch {
|
|
throw new Error(
|
|
`Could not find a Chrome executable at ${chromePath}. Pass --chrome-path or set ORACLE_SI_CHROME_PATH to a valid Chrome installation.`
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function main(argv = process.argv.slice(2)) {
|
|
const args = parseArgs(argv);
|
|
if (args.help === "true") {
|
|
console.log(usage());
|
|
return;
|
|
}
|
|
const runtimeHome = resolveRuntimeHome(args["runtime-home"]);
|
|
await ensureRuntimeTree(runtimeHome);
|
|
const statePath = resolveStateWritePath({ explicitPath: args["state-path"], runtimeHome });
|
|
const metaPath = resolveMetaWritePath({ explicitPath: args["meta-path"], runtimeHome });
|
|
const profileDir = resolveProfileDir({ explicitPath: args["profile-dir"], runtimeHome });
|
|
const chromePath = resolveChromePath(args["chrome-path"]);
|
|
const targetUrl = args.url || process.env.ORACLE_SI_TARGET_URL || DEFAULT_URL;
|
|
const timeoutMs = Number(args["timeout-ms"] || 10 * 60 * 1000);
|
|
|
|
await ensureChromeExecutable(chromePath);
|
|
const { runtime: playwrightRuntime, sourceLabel } = await loadPlaywright();
|
|
const { chromium } = playwrightRuntime;
|
|
|
|
await ensureParentDir(statePath);
|
|
await ensureParentDir(metaPath);
|
|
await ensureDir(profileDir);
|
|
|
|
console.log(`Opening Chrome profile at ${profileDir}`);
|
|
console.log(`Using browser automation runtime: ${sourceLabel}`);
|
|
console.log("Complete Oracle login in the opened window if prompted.");
|
|
|
|
const context = await chromium.launchPersistentContext(profileDir, {
|
|
executablePath: chromePath,
|
|
headless: false,
|
|
ignoreHTTPSErrors: true,
|
|
viewport: { width: 1600, height: 900 },
|
|
});
|
|
|
|
let headerMeta = null;
|
|
const requestListener = (request) => {
|
|
if (headerMeta) return;
|
|
if (!request.url().includes("/ui/dv/ui/api/")) return;
|
|
const headers = request.headers();
|
|
const csrfToken = headers["x-csrf-token"] || null;
|
|
const clientBin = headers["x-bitech-clientbin"] || null;
|
|
if (!csrfToken || !clientBin) return;
|
|
headerMeta = {
|
|
xCsrfToken: csrfToken,
|
|
xBitechClientbin: clientBin,
|
|
referer: headers.referer || targetUrl,
|
|
targetUrl,
|
|
capturedAt: new Date().toISOString(),
|
|
};
|
|
};
|
|
context.on("request", requestListener);
|
|
|
|
try {
|
|
const page = context.pages()[0] ?? (await context.newPage());
|
|
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 120000 });
|
|
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (Date.now() < deadline) {
|
|
await page.waitForTimeout(2000);
|
|
const url = page.url();
|
|
const title = await page.title().catch(() => "");
|
|
console.log(`Waiting for Oracle Analytics session: ${url}`);
|
|
|
|
const authenticated =
|
|
url.startsWith("https://salesintelligence-dv.oracle.com/") &&
|
|
!url.includes("signon.oracle.com") &&
|
|
!url.includes("signon-int.oracle.com") &&
|
|
/Oracle Analytics|LAD Knowledge One View/i.test(title || url);
|
|
|
|
if (!authenticated) continue;
|
|
|
|
const html = await page.content();
|
|
const fallbackClientBin = extractClientBin(html);
|
|
|
|
if (!headerMeta) {
|
|
await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {});
|
|
await page.waitForTimeout(4000);
|
|
}
|
|
|
|
if (!headerMeta?.xBitechClientbin && fallbackClientBin) {
|
|
headerMeta = {
|
|
...(headerMeta || {}),
|
|
xCsrfToken: headerMeta?.xCsrfToken || null,
|
|
xBitechClientbin: fallbackClientBin,
|
|
referer: headerMeta?.referer || targetUrl,
|
|
targetUrl,
|
|
capturedAt: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
if (!headerMeta?.xCsrfToken || !headerMeta?.xBitechClientbin) {
|
|
continue;
|
|
}
|
|
|
|
const state = await context.storageState();
|
|
await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
await fs.writeFile(metaPath, `${JSON.stringify(headerMeta, null, 2)}\n`, "utf8");
|
|
|
|
console.log(`Saved auth state to ${statePath}`);
|
|
console.log(`Saved session metadata to ${metaPath}`);
|
|
return;
|
|
}
|
|
|
|
throw new Error(`Timed out waiting for an authenticated Oracle DV page after ${timeoutMs}ms.`);
|
|
} finally {
|
|
context.off("request", requestListener);
|
|
await context.close().catch(() => {});
|
|
}
|
|
}
|
|
|
|
const entryUrl = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
|
|
if (import.meta.url === entryUrl) {
|
|
main().catch((error) => {
|
|
console.error(error.message);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|