324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
(function attachBackgroundPanelBridge(root, factory) {
|
|
root.MultiPageBackgroundPanelBridge = factory();
|
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPanelBridgeModule() {
|
|
function createPanelBridge(deps = {}) {
|
|
const {
|
|
chrome,
|
|
addLog,
|
|
closeConflictingTabsForSource,
|
|
ensureContentScriptReadyOnTab,
|
|
getPanelMode,
|
|
normalizeCodex2ApiUrl,
|
|
normalizeSub2ApiUrl,
|
|
rememberSourceLastUrl,
|
|
sendToContentScript,
|
|
sendToContentScriptResilient,
|
|
waitForTabUrlFamily,
|
|
DEFAULT_SUB2API_GROUP_NAME,
|
|
SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
|
|
} = deps;
|
|
|
|
function normalizeAdminKey(value = '') {
|
|
return String(value || '').trim();
|
|
}
|
|
|
|
function extractStateFromAuthUrl(authUrl = '') {
|
|
try {
|
|
return new URL(authUrl).searchParams.get('state') || '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function getCodex2ApiErrorMessage(payload, responseStatus = 500) {
|
|
const candidates = [
|
|
payload?.error,
|
|
payload?.message,
|
|
payload?.detail,
|
|
payload?.reason,
|
|
];
|
|
const message = candidates
|
|
.map((value) => String(value || '').trim())
|
|
.find(Boolean);
|
|
return message || `Codex2API 请求失败(HTTP ${responseStatus})。`;
|
|
}
|
|
|
|
function deriveCpaManagementOrigin(vpsUrl) {
|
|
const normalizedUrl = String(vpsUrl || '').trim();
|
|
if (!normalizedUrl) {
|
|
throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。');
|
|
}
|
|
let parsed;
|
|
try {
|
|
parsed = new URL(normalizedUrl);
|
|
} catch {
|
|
throw new Error('CPA 地址格式无效,请先在侧边栏检查。');
|
|
}
|
|
return parsed.origin;
|
|
}
|
|
|
|
function getCpaApiErrorMessage(payload, responseStatus = 500) {
|
|
const candidates = [
|
|
payload?.error,
|
|
payload?.message,
|
|
payload?.detail,
|
|
payload?.reason,
|
|
];
|
|
const message = candidates
|
|
.map((value) => String(value || '').trim())
|
|
.find(Boolean);
|
|
return message || `CPA 管理接口请求失败(HTTP ${responseStatus})。`;
|
|
}
|
|
|
|
async function fetchCpaManagementJson(origin, path, options = {}) {
|
|
const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 20000));
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
|
|
try {
|
|
const managementKey = String(options.managementKey || '').trim();
|
|
const headers = {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
};
|
|
if (managementKey) {
|
|
headers.Authorization = `Bearer ${managementKey}`;
|
|
headers['X-Management-Key'] = managementKey;
|
|
}
|
|
|
|
const response = await fetch(`${origin}${path}`, {
|
|
method: options.method || 'POST',
|
|
headers,
|
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
signal: controller.signal,
|
|
});
|
|
|
|
let payload = {};
|
|
try {
|
|
payload = await response.json();
|
|
} catch {
|
|
payload = {};
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(getCpaApiErrorMessage(payload, response.status));
|
|
}
|
|
|
|
return payload;
|
|
} catch (error) {
|
|
if (error?.name === 'AbortError') {
|
|
throw new Error('CPA 管理接口请求超时,请稍后重试。');
|
|
}
|
|
throw error;
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
async function fetchCodex2ApiJson(origin, path, options = {}) {
|
|
const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 30000));
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
|
|
try {
|
|
const response = await fetch(`${origin}${path}`, {
|
|
method: options.method || 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-Admin-Key': normalizeAdminKey(options.adminKey),
|
|
},
|
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
signal: controller.signal,
|
|
});
|
|
|
|
let payload = {};
|
|
try {
|
|
payload = await response.json();
|
|
} catch {
|
|
payload = {};
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(getCodex2ApiErrorMessage(payload, response.status));
|
|
}
|
|
|
|
return payload;
|
|
} catch (error) {
|
|
if (error?.name === 'AbortError') {
|
|
throw new Error('Codex2API 请求超时,请稍后重试。');
|
|
}
|
|
throw error;
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
async function requestOAuthUrlFromPanel(state, options = {}) {
|
|
if (getPanelMode(state) === 'codex2api') {
|
|
return requestCodex2ApiOAuthUrl(state, options);
|
|
}
|
|
if (getPanelMode(state) === 'sub2api') {
|
|
return requestSub2ApiOAuthUrl(state, options);
|
|
}
|
|
return requestCpaOAuthUrl(state, options);
|
|
}
|
|
|
|
async function requestCpaOAuthUrl(state, options = {}) {
|
|
const { logLabel = 'OAuth 刷新' } = options;
|
|
if (!state.vpsUrl) {
|
|
throw new Error('尚未配置 CPA 地址,请先在侧边栏填写。');
|
|
}
|
|
const managementKey = String(state.vpsPassword || '').trim();
|
|
if (!managementKey) {
|
|
throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。');
|
|
}
|
|
|
|
const origin = deriveCpaManagementOrigin(state.vpsUrl);
|
|
|
|
await addLog(`${logLabel}:正在通过 CPA 管理接口获取 OAuth 授权链接...`);
|
|
const result = await fetchCpaManagementJson(origin, '/v0/management/codex-auth-url', {
|
|
method: 'GET',
|
|
managementKey,
|
|
});
|
|
|
|
const oauthUrl = String(
|
|
result?.url
|
|
|| result?.auth_url
|
|
|| result?.authUrl
|
|
|| result?.data?.url
|
|
|| result?.data?.auth_url
|
|
|| result?.data?.authUrl
|
|
|| ''
|
|
).trim();
|
|
const oauthState = String(
|
|
result?.state
|
|
|| result?.auth_state
|
|
|| result?.authState
|
|
|| result?.data?.state
|
|
|| result?.data?.auth_state
|
|
|| result?.data?.authState
|
|
|| ''
|
|
).trim()
|
|
|| extractStateFromAuthUrl(oauthUrl);
|
|
|
|
if (!oauthUrl || !oauthUrl.startsWith('http')) {
|
|
throw new Error('CPA 管理接口未返回有效的 auth_url。');
|
|
}
|
|
|
|
return {
|
|
oauthUrl,
|
|
cpaOAuthState: oauthState || null,
|
|
cpaManagementOrigin: origin,
|
|
};
|
|
}
|
|
|
|
async function requestCodex2ApiOAuthUrl(state, options = {}) {
|
|
const { logLabel = 'OAuth 刷新' } = options;
|
|
const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl);
|
|
const adminKey = normalizeAdminKey(state.codex2apiAdminKey);
|
|
|
|
if (!adminKey) {
|
|
throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。');
|
|
}
|
|
|
|
const origin = new URL(codex2apiUrl).origin;
|
|
await addLog(`${logLabel}:正在通过 Codex2API 协议生成 OAuth 授权链接...`);
|
|
|
|
const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/generate-auth-url', {
|
|
adminKey,
|
|
method: 'POST',
|
|
body: {},
|
|
});
|
|
|
|
const oauthUrl = String(result?.auth_url || result?.authUrl || '').trim();
|
|
const sessionId = String(result?.session_id || result?.sessionId || '').trim();
|
|
const oauthState = extractStateFromAuthUrl(oauthUrl);
|
|
|
|
if (!oauthUrl || !sessionId) {
|
|
throw new Error('Codex2API 未返回有效的 auth_url 或 session_id。');
|
|
}
|
|
|
|
return {
|
|
oauthUrl,
|
|
codex2apiSessionId: sessionId,
|
|
codex2apiOAuthState: oauthState || null,
|
|
};
|
|
}
|
|
|
|
async function requestSub2ApiOAuthUrl(state, options = {}) {
|
|
const { logLabel = 'OAuth 刷新' } = options;
|
|
const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl);
|
|
const groupName = (state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME).trim() || DEFAULT_SUB2API_GROUP_NAME;
|
|
|
|
if (!sub2apiUrl) {
|
|
throw new Error('SUB2API URL is not configured. Please fill it in the side panel first.');
|
|
}
|
|
if (!state.sub2apiEmail) {
|
|
throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。');
|
|
}
|
|
if (!state.sub2apiPassword) {
|
|
throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。');
|
|
}
|
|
|
|
await addLog(`${logLabel}:正在打开 SUB2API 后台...`);
|
|
|
|
const injectFiles = ['content/utils.js', 'content/sub2api-panel.js'];
|
|
await closeConflictingTabsForSource('sub2api-panel', sub2apiUrl);
|
|
|
|
const tab = await chrome.tabs.create({ url: sub2apiUrl, active: true });
|
|
const tabId = tab.id;
|
|
await rememberSourceLastUrl('sub2api-panel', sub2apiUrl);
|
|
|
|
await addLog(`${logLabel}:SUB2API 页面已打开,正在等待页面进入目标地址...`);
|
|
const matchedTab = await waitForTabUrlFamily('sub2api-panel', tabId, sub2apiUrl, {
|
|
timeoutMs: 15000,
|
|
retryDelayMs: 400,
|
|
});
|
|
if (!matchedTab) {
|
|
await addLog(`${logLabel}:SUB2API 页面尚未稳定,继续尝试连接内容脚本...`, 'warn');
|
|
}
|
|
|
|
await ensureContentScriptReadyOnTab('sub2api-panel', tabId, {
|
|
inject: injectFiles,
|
|
injectSource: 'sub2api-panel',
|
|
timeoutMs: 45000,
|
|
retryDelayMs: 900,
|
|
logMessage: `${logLabel}:SUB2API 页面仍在加载,正在重试连接内容脚本...`,
|
|
});
|
|
|
|
const result = await sendToContentScript('sub2api-panel', {
|
|
type: 'REQUEST_OAUTH_URL',
|
|
source: 'background',
|
|
payload: {
|
|
sub2apiUrl,
|
|
sub2apiEmail: state.sub2apiEmail,
|
|
sub2apiPassword: state.sub2apiPassword,
|
|
sub2apiGroupName: groupName,
|
|
sub2apiDefaultProxyName: state.sub2apiDefaultProxyName,
|
|
sub2apiAccountPriority: state.sub2apiAccountPriority,
|
|
logStep: 7,
|
|
},
|
|
}, {
|
|
responseTimeoutMs: SUB2API_STEP1_RESPONSE_TIMEOUT_MS,
|
|
});
|
|
|
|
if (result?.error) {
|
|
throw new Error(result.error);
|
|
}
|
|
return result || {};
|
|
}
|
|
|
|
return {
|
|
requestOAuthUrlFromPanel,
|
|
requestCodex2ApiOAuthUrl,
|
|
requestCpaOAuthUrl,
|
|
requestSub2ApiOAuthUrl,
|
|
};
|
|
}
|
|
|
|
return {
|
|
createPanelBridge,
|
|
};
|
|
});
|