376 lines
14 KiB
JavaScript
376 lines
14 KiB
JavaScript
(function attachBackgroundStep6(root, factory) {
|
||
root.MultiPageBackgroundStep6 = factory();
|
||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep6Module() {
|
||
const DEFAULT_REGISTRATION_SUCCESS_WAIT_MS = 4000;
|
||
const LOCAL_CPA_JSON_NO_RT_PANEL_MODE = 'local-cpa-json-no-rt';
|
||
const LOCAL_CPA_JSON_EXPORT_NODE_ID = 'local-cpa-json-export';
|
||
const CHATGPT_SESSION_EXPORT_URL = 'https://chatgpt.com/';
|
||
const STEP6_COOKIE_CLEAR_DOMAINS = [
|
||
'chatgpt.com',
|
||
'chat.openai.com',
|
||
'pay.openai.com',
|
||
'openai.com',
|
||
'auth.openai.com',
|
||
'auth0.openai.com',
|
||
'accounts.openai.com',
|
||
'paypal.com',
|
||
'stripe.com',
|
||
'checkout.stripe.com',
|
||
'meiguodizhi.com',
|
||
'mail-api.yuecheng.shop',
|
||
'yuecheng.shop',
|
||
];
|
||
const STEP6_COOKIE_CLEAR_ORIGINS = [
|
||
'https://chatgpt.com',
|
||
'https://chat.openai.com',
|
||
'https://pay.openai.com',
|
||
'https://auth.openai.com',
|
||
'https://auth0.openai.com',
|
||
'https://accounts.openai.com',
|
||
'https://openai.com',
|
||
'https://www.paypal.com',
|
||
'https://paypal.com',
|
||
'https://checkout.stripe.com',
|
||
'https://www.meiguodizhi.com',
|
||
'https://meiguodizhi.com',
|
||
'https://mail-api.yuecheng.shop',
|
||
];
|
||
|
||
function normalizeStep6CookieDomain(domain) {
|
||
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
|
||
}
|
||
|
||
function shouldClearStep6Cookie(cookie) {
|
||
const domain = normalizeStep6CookieDomain(cookie?.domain);
|
||
if (!domain) return false;
|
||
return STEP6_COOKIE_CLEAR_DOMAINS.some((target) => (
|
||
domain === target || domain.endsWith(`.${target}`)
|
||
));
|
||
}
|
||
|
||
function buildStep6CookieRemovalUrl(cookie) {
|
||
const host = normalizeStep6CookieDomain(cookie?.domain);
|
||
const rawPath = String(cookie?.path || '/');
|
||
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
|
||
return `https://${host}${path}`;
|
||
}
|
||
|
||
async function collectStep6Cookies(chromeApi) {
|
||
if (!chromeApi.cookies?.getAll) {
|
||
return [];
|
||
}
|
||
|
||
const stores = chromeApi.cookies.getAllCookieStores
|
||
? await chromeApi.cookies.getAllCookieStores()
|
||
: [{ id: undefined }];
|
||
const cookies = [];
|
||
const seen = new Set();
|
||
|
||
for (const store of stores) {
|
||
const storeId = store?.id;
|
||
const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {});
|
||
for (const cookie of batch || []) {
|
||
if (!shouldClearStep6Cookie(cookie)) continue;
|
||
const key = [
|
||
cookie.storeId || storeId || '',
|
||
cookie.domain || '',
|
||
cookie.path || '',
|
||
cookie.name || '',
|
||
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
|
||
].join('|');
|
||
if (seen.has(key)) continue;
|
||
seen.add(key);
|
||
cookies.push(cookie);
|
||
}
|
||
}
|
||
|
||
return cookies;
|
||
}
|
||
|
||
async function removeStep6Cookie(chromeApi, cookie, getErrorMessage) {
|
||
const details = {
|
||
url: buildStep6CookieRemovalUrl(cookie),
|
||
name: cookie.name,
|
||
};
|
||
if (cookie.storeId) {
|
||
details.storeId = cookie.storeId;
|
||
}
|
||
if (cookie.partitionKey) {
|
||
details.partitionKey = cookie.partitionKey;
|
||
}
|
||
|
||
try {
|
||
const result = await chromeApi.cookies.remove(details);
|
||
return Boolean(result);
|
||
} catch (error) {
|
||
console.warn('[MultiPage:step6] remove cookie failed', {
|
||
domain: cookie?.domain,
|
||
name: cookie?.name,
|
||
message: getErrorMessage(error),
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function createStep6Executor(deps = {}) {
|
||
const {
|
||
addLog = async () => {},
|
||
buildLocalHelperEndpoint = null,
|
||
chrome: chromeApi = globalThis.chrome,
|
||
completeNodeFromBackground,
|
||
createLocalCliProxyApi = null,
|
||
ensureContentScriptReadyOnTab = async () => {},
|
||
getErrorMessage = (error) => error?.message || String(error || '未知错误'),
|
||
getPanelMode = (state = {}) => String(state?.panelMode || '').trim() || 'cpa',
|
||
getTabId = async () => null,
|
||
normalizeHotmailLocalBaseUrl = (value) => String(value || '').trim(),
|
||
registrationSuccessWaitMs = DEFAULT_REGISTRATION_SUCCESS_WAIT_MS,
|
||
sessionExportInjectFiles = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'],
|
||
sendToContentScriptResilient = null,
|
||
sleepWithStop = async (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0))),
|
||
} = deps;
|
||
|
||
function normalizeString(value = '') {
|
||
return String(value || '').trim();
|
||
}
|
||
|
||
function isLocalCpaJsonNoRtMode(state = {}) {
|
||
return normalizeString(getPanelMode(state)) === LOCAL_CPA_JSON_NO_RT_PANEL_MODE;
|
||
}
|
||
|
||
function getLocalCliProxyApi() {
|
||
const factory = createLocalCliProxyApi
|
||
|| globalThis.MultiPageBackgroundLocalCliProxyApi?.createLocalCliProxyApi
|
||
|| null;
|
||
if (typeof factory !== 'function') {
|
||
throw new Error('本地 CPA JSON 无RT 模块未加载,无法导出认证文件。');
|
||
}
|
||
return factory({
|
||
crypto: globalThis.crypto,
|
||
fetch: typeof globalThis.fetch === 'function' ? globalThis.fetch.bind(globalThis) : null,
|
||
sessionToJsonConverter: globalThis.MultiPageSessionToJsonConverter,
|
||
});
|
||
}
|
||
|
||
async function saveLocalCpaJsonArtifactViaHelper(helperBaseUrl, artifact) {
|
||
const endpoint = typeof buildLocalHelperEndpoint === 'function'
|
||
? buildLocalHelperEndpoint(helperBaseUrl, '/save-auth-json')
|
||
: new URL('/save-auth-json', `${helperBaseUrl.replace(/\/+$/, '')}/`).toString();
|
||
const response = await fetch(endpoint, {
|
||
method: 'POST',
|
||
headers: {
|
||
Accept: 'application/json',
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
filePath: artifact.filePath,
|
||
directoryPath: artifact.directoryPath,
|
||
content: artifact.jsonText,
|
||
}),
|
||
});
|
||
|
||
let payload = {};
|
||
try {
|
||
payload = await response.json();
|
||
} catch {
|
||
payload = {};
|
||
}
|
||
|
||
if (!response.ok || payload?.ok === false) {
|
||
const helperError = normalizeString(payload?.error);
|
||
if (/Missing email\/clientId\/refreshToken/i.test(helperError)) {
|
||
throw new Error('本地 helper 未识别 /save-auth-json,当前运行的 hotmail_helper.py 版本过旧或不是当前项目目录。请停止旧 helper,并从当前 FlowPilot-FlowPilot1.0.2 目录重新启动本地助手。');
|
||
}
|
||
throw new Error(helperError || `本地 helper 写入失败(HTTP ${response.status})。`);
|
||
}
|
||
|
||
return {
|
||
...artifact,
|
||
filePath: normalizeString(payload?.filePath) || artifact.filePath,
|
||
};
|
||
}
|
||
|
||
async function openChatGptSessionExportTab(state = {}) {
|
||
if (chromeApi?.tabs?.create) {
|
||
const tab = await chromeApi.tabs.create({
|
||
url: CHATGPT_SESSION_EXPORT_URL,
|
||
active: false,
|
||
});
|
||
const tabId = Number(tab?.id);
|
||
if (Number.isInteger(tabId) && tabId > 0) {
|
||
return {
|
||
source: 'plus-checkout',
|
||
tabId,
|
||
temporary: true,
|
||
};
|
||
}
|
||
}
|
||
|
||
const fallbackTabId = Number(state?.plusCheckoutTabId || await getTabId('plus-checkout') || await getTabId('signup-page'));
|
||
if (!Number.isInteger(fallbackTabId) || fallbackTabId <= 0) {
|
||
throw new Error('未找到可读取 ChatGPT 会话的标签页,无法导出本地 CPA JSON 无RT。');
|
||
}
|
||
return {
|
||
source: 'plus-checkout',
|
||
tabId: fallbackTabId,
|
||
temporary: false,
|
||
};
|
||
}
|
||
|
||
async function closeTemporarySessionExportTab(tabInfo = {}) {
|
||
if (!tabInfo?.temporary || !Number.isInteger(Number(tabInfo?.tabId)) || !chromeApi?.tabs?.remove) {
|
||
return;
|
||
}
|
||
await chromeApi.tabs.remove(Number(tabInfo.tabId)).catch(() => {});
|
||
}
|
||
|
||
async function readChatGptSessionForExport(state = {}, visibleStep = 7) {
|
||
if (typeof sendToContentScriptResilient !== 'function') {
|
||
throw new Error('当前环境缺少 ChatGPT 会话读取通道,无法导出本地 CPA JSON 无RT。');
|
||
}
|
||
|
||
const tabInfo = await openChatGptSessionExportTab(state);
|
||
try {
|
||
await ensureContentScriptReadyOnTab(tabInfo.source, tabInfo.tabId, {
|
||
inject: sessionExportInjectFiles,
|
||
injectSource: tabInfo.source,
|
||
timeoutMs: 30000,
|
||
retryDelayMs: 800,
|
||
logMessage: `步骤 ${visibleStep}:正在连接 ChatGPT 页面,准备读取当前会话并导出 JSON...`,
|
||
logStep: visibleStep,
|
||
logStepKey: LOCAL_CPA_JSON_EXPORT_NODE_ID,
|
||
});
|
||
|
||
const sessionResult = await sendToContentScriptResilient(tabInfo.source, {
|
||
type: 'PLUS_CHECKOUT_GET_STATE',
|
||
step: visibleStep,
|
||
source: 'background',
|
||
payload: {
|
||
includeSession: true,
|
||
includeAccessToken: true,
|
||
},
|
||
}, {
|
||
timeoutMs: 15000,
|
||
retryDelayMs: 500,
|
||
logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 页面返回当前登录会话...`,
|
||
logStep: visibleStep,
|
||
logStepKey: LOCAL_CPA_JSON_EXPORT_NODE_ID,
|
||
});
|
||
|
||
if (sessionResult?.error) {
|
||
throw new Error(sessionResult.error);
|
||
}
|
||
return sessionResult;
|
||
} finally {
|
||
await closeTemporarySessionExportTab(tabInfo);
|
||
}
|
||
}
|
||
|
||
async function exportLocalCpaJsonNoRt(state = {}, options = {}) {
|
||
const visibleStep = Math.max(1, Math.floor(Number(options.visibleStep) || 7));
|
||
const helperBaseUrl = normalizeHotmailLocalBaseUrl(state.hotmailLocalBaseUrl);
|
||
const pluginDir = normalizeString(state.localCpaJsonPluginDir);
|
||
if (!helperBaseUrl) {
|
||
throw new Error('尚未配置 Hotmail 本地助手地址,请先在侧边栏填写。');
|
||
}
|
||
if (!pluginDir) {
|
||
throw new Error('尚未配置本地插件目录,请先在侧边栏填写。');
|
||
}
|
||
|
||
const sessionResult = await readChatGptSessionForExport(state, visibleStep);
|
||
const api = getLocalCliProxyApi();
|
||
const artifact = await api.buildAuthJsonArtifact({
|
||
pluginDir,
|
||
relativeAuthDir: state.localCpaJsonRelativeAuthDir,
|
||
session: sessionResult?.session,
|
||
accessToken: sessionResult?.accessToken,
|
||
sessionToken: sessionResult?.session?.sessionToken,
|
||
email: sessionResult?.email || sessionResult?.session?.user?.email || state?.email,
|
||
expiresAt: sessionResult?.expiresAt || sessionResult?.session?.expires,
|
||
accountId: sessionResult?.session?.account?.id,
|
||
userId: sessionResult?.session?.user?.id,
|
||
planType: sessionResult?.session?.account?.planType,
|
||
lastRefresh: '',
|
||
sourceName: 'SessionToJson Local No RT',
|
||
});
|
||
|
||
for (const warning of Array.isArray(artifact.warnings) ? artifact.warnings : []) {
|
||
await addLog(`步骤 ${visibleStep}:${warning}`, 'warn');
|
||
}
|
||
|
||
const saved = await saveLocalCpaJsonArtifactViaHelper(helperBaseUrl, artifact);
|
||
const verifiedStatus = `本地CPA JSON 无RT 已导出:${saved.filePath}`;
|
||
await addLog(`步骤 ${visibleStep}:${verifiedStatus}`, 'ok');
|
||
return {
|
||
verifiedStatus,
|
||
localCpaJsonFilePath: saved.filePath,
|
||
};
|
||
}
|
||
|
||
async function clearCookiesIfEnabled(state = {}) {
|
||
if (!state?.step6CookieCleanupEnabled) {
|
||
return;
|
||
}
|
||
if (!chromeApi?.cookies?.getAll || !chromeApi.cookies?.remove) {
|
||
await addLog('步骤 6:当前浏览器不支持 cookies API,跳过第六步 Cookies 清理。', 'warn');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await addLog('步骤 6:已开启 Cookies 清理,正在清理 ChatGPT / OpenAI cookies...', 'info');
|
||
const cookies = await collectStep6Cookies(chromeApi);
|
||
let removedCount = 0;
|
||
for (const cookie of cookies) {
|
||
if (await removeStep6Cookie(chromeApi, cookie, getErrorMessage)) {
|
||
removedCount += 1;
|
||
}
|
||
}
|
||
|
||
if (chromeApi.browsingData?.removeCookies) {
|
||
try {
|
||
await chromeApi.browsingData.removeCookies({
|
||
since: 0,
|
||
origins: STEP6_COOKIE_CLEAR_ORIGINS,
|
||
});
|
||
} catch (error) {
|
||
await addLog(`步骤 6:browsingData 补扫 cookies 失败:${getErrorMessage(error)}`, 'warn');
|
||
}
|
||
}
|
||
|
||
await addLog(`步骤 6:已清理 ${removedCount} 个 ChatGPT / OpenAI cookies。`, 'ok');
|
||
} catch (error) {
|
||
await addLog(`步骤 6:Cookies 清理失败,已跳过并继续后续流程:${getErrorMessage(error)}`, 'warn');
|
||
}
|
||
}
|
||
|
||
async function executeStep6(state = {}) {
|
||
const baseWaitMs = Math.max(0, Math.floor(Number(registrationSuccessWaitMs) || 0));
|
||
const waitMs = baseWaitMs;
|
||
if (waitMs > 0) {
|
||
await addLog(`步骤 6:等待 ${Math.round(waitMs / 1000)} 秒,确认注册成功并让页面稳定...`, 'info');
|
||
await sleepWithStop(waitMs);
|
||
}
|
||
await clearCookiesIfEnabled(state);
|
||
await addLog('步骤 6:注册成功等待完成,注册阶段已结束。', 'ok');
|
||
await completeNodeFromBackground('wait-registration-success', {});
|
||
}
|
||
|
||
async function executeLocalCpaJsonNoRtExport(state = {}) {
|
||
if (!isLocalCpaJsonNoRtMode(state)) {
|
||
throw new Error('当前不是本地CPA JSON 无RT 模式,不能执行无RT导出节点。');
|
||
}
|
||
await addLog('步骤 7:Plus Checkout 已完成,等待 5 秒后导出本地 CPA JSON 无RT...', 'info');
|
||
await sleepWithStop(5000);
|
||
const completionPayload = await exportLocalCpaJsonNoRt(state, { visibleStep: 7 });
|
||
await completeNodeFromBackground(LOCAL_CPA_JSON_EXPORT_NODE_ID, completionPayload);
|
||
}
|
||
|
||
return {
|
||
executeLocalCpaJsonNoRtExport,
|
||
executeStep6,
|
||
};
|
||
}
|
||
|
||
return { createStep6Executor };
|
||
});
|