9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
145 lines
5.9 KiB
JavaScript
145 lines
5.9 KiB
JavaScript
/**
|
||
* ONES 旧版 Project API — 经 Browser Bridge 在已登录标签页内 fetch(携带 Cookie)。
|
||
* 文档:https://developer.ones.cn/zh-CN/docs/api/readme/
|
||
*/
|
||
import { CliError } from '@jackwener/opencli/errors';
|
||
export const API_PREFIX = '/project/api/project';
|
||
export function getOnesBaseUrl() {
|
||
const u = process.env.ONES_BASE_URL?.trim().replace(/\/+$/, '');
|
||
if (!u) {
|
||
throw new CliError('CONFIG', 'Missing ONES_BASE_URL', 'Set ONES_BASE_URL to your deployment origin, e.g. https://your-team.ones.cn (no trailing slash).');
|
||
}
|
||
return u;
|
||
}
|
||
export function onesApiUrl(apiPath) {
|
||
const base = getOnesBaseUrl();
|
||
const p = apiPath.replace(/^\/+/, '');
|
||
return `${base}${API_PREFIX}/${p}`;
|
||
}
|
||
/** 打开 ONES 根地址,确保后续 fetch 与页面同源、带上登录 Cookie */
|
||
export async function gotoOnesHome(page) {
|
||
await page.goto(getOnesBaseUrl(), { waitUntil: 'load' });
|
||
await page.wait(2);
|
||
}
|
||
/**
|
||
* 在页面内发起请求。默认带 credentials;若设置了 ONES_USER_ID + ONES_AUTH_TOKEN,则附加文档要求的 Header(与纯 Cookie 二选一或并存,取决于部署)。
|
||
*/
|
||
function buildHeaders(auth, includeJsonContentType) {
|
||
const ref = getOnesBaseUrl();
|
||
const out = { Referer: ref };
|
||
if (auth) {
|
||
const uid = process.env.ONES_USER_ID?.trim() ||
|
||
process.env.ONES_USER_UUID?.trim() ||
|
||
process.env.Ones_User_Id?.trim();
|
||
const tok = process.env.ONES_AUTH_TOKEN?.trim() || process.env.Ones_Auth_Token?.trim();
|
||
if (uid && tok) {
|
||
out['Ones-User-Id'] = uid;
|
||
out['Ones-Auth-Token'] = tok;
|
||
}
|
||
}
|
||
if (includeJsonContentType)
|
||
out['Content-Type'] = 'application/json';
|
||
return out;
|
||
}
|
||
export function summarizeOnesError(status, body) {
|
||
if (body && typeof body === 'object') {
|
||
const o = body;
|
||
const parts = [];
|
||
if (typeof o.type === 'string')
|
||
parts.push(o.type);
|
||
if (typeof o.reason === 'string')
|
||
parts.push(o.reason);
|
||
if (typeof o.errcode === 'string')
|
||
parts.push(o.errcode);
|
||
if (typeof o.message === 'string')
|
||
parts.push(o.message);
|
||
if (o.code !== undefined && o.code !== null)
|
||
parts.push(`code=${String(o.code)}`);
|
||
if (parts.length)
|
||
return parts.filter(Boolean).join(' · ');
|
||
}
|
||
return status === 401 ? 'Unauthorized' : `HTTP ${status}`;
|
||
}
|
||
/** ONES 部分接口 HTTP 200 但 body 仍为错误(如 reason: ServerError) */
|
||
function throwIfOnesPeekBusinessError(apiPath, parsed) {
|
||
if (parsed === null || typeof parsed !== 'object')
|
||
return;
|
||
const o = parsed;
|
||
if (Array.isArray(o.groups))
|
||
return;
|
||
const hasErr = (typeof o.reason === 'string' && o.reason.length > 0) ||
|
||
(typeof o.errcode === 'string' && o.errcode.length > 0) ||
|
||
(typeof o.type === 'string' && o.type.length > 0);
|
||
if (!hasErr)
|
||
return;
|
||
const detail = summarizeOnesError(200, parsed);
|
||
throw new CliError('FETCH_ERROR', `ONES ${apiPath}: ${detail}`, '若 query 不合法会返回 ServerError;可试 opencli ones tasks(空 must)或检查筛选器文档。响应全文可用 -v 或临时打日志。');
|
||
}
|
||
export async function onesFetchInPageWithMeta(page, apiPath, options = {}) {
|
||
if (!options.skipGoto) {
|
||
await gotoOnesHome(page);
|
||
}
|
||
const url = onesApiUrl(apiPath);
|
||
const method = (options.method ?? 'GET').toUpperCase();
|
||
const auth = options.auth !== false;
|
||
const body = options.body ?? null;
|
||
const includeCt = body !== null || method === 'POST' || method === 'PUT' || method === 'PATCH';
|
||
const headers = buildHeaders(auth, includeCt);
|
||
const urlJs = JSON.stringify(url);
|
||
const methodJs = JSON.stringify(method);
|
||
const headersJs = JSON.stringify(headers);
|
||
const bodyJs = body === null ? 'null' : JSON.stringify(body);
|
||
const raw = await page.evaluate(`
|
||
(async () => {
|
||
const url = ${urlJs};
|
||
const method = ${methodJs};
|
||
const headers = ${headersJs};
|
||
const body = ${bodyJs};
|
||
const init = {
|
||
method,
|
||
headers: { ...headers },
|
||
credentials: 'include',
|
||
};
|
||
if (body !== null) init.body = body;
|
||
const res = await fetch(url, init);
|
||
const text = await res.text();
|
||
let parsed = null;
|
||
try {
|
||
parsed = text ? JSON.parse(text) : null;
|
||
} catch {
|
||
parsed = text;
|
||
}
|
||
return { ok: res.ok, status: res.status, parsed };
|
||
})()
|
||
`);
|
||
return raw;
|
||
}
|
||
/** 当前操作用户 8 位 uuid(Header 或 GET users/me) */
|
||
export async function resolveOnesUserUuid(page, opts) {
|
||
const fromEnv = process.env.ONES_USER_ID?.trim() ||
|
||
process.env.ONES_USER_UUID?.trim() ||
|
||
process.env.Ones_User_Id?.trim();
|
||
if (fromEnv)
|
||
return fromEnv;
|
||
const data = (await onesFetchInPage(page, 'users/me', { skipGoto: opts?.skipGoto }));
|
||
const u = data.user && typeof data.user === 'object' ? data.user : data;
|
||
if (!u || typeof u.uuid !== 'string') {
|
||
throw new CliError('FETCH_ERROR', 'Could not read current user uuid from users/me', 'Set ONES_USER_ID or ensure Chrome is logged in; try: opencli ones me -f json');
|
||
}
|
||
return String(u.uuid);
|
||
}
|
||
export async function onesFetchInPage(page, apiPath, options = {}) {
|
||
const r = await onesFetchInPageWithMeta(page, apiPath, options);
|
||
if (!r.ok) {
|
||
const detail = summarizeOnesError(r.status, r.parsed);
|
||
const hint = r.status === 401
|
||
? '在 Chrome 中打开 ONES 并登录;或先执行 opencli ones login 后按提示 export ONES_USER_ID / ONES_AUTH_TOKEN;并确认 ONES_BASE_URL 与浏览器地址一致。'
|
||
: '检查 ONES_BASE_URL、VPN/内网,以及实例是否仍为 Project API 路径。';
|
||
throw new CliError('FETCH_ERROR', `ONES ${apiPath}: ${detail}`, hint);
|
||
}
|
||
if (apiPath.includes('/filters/peek')) {
|
||
throwIfOnesPeekBusinessError(apiPath, r.parsed);
|
||
}
|
||
return r.parsed;
|
||
}
|