refactor flows into canonical runtime architecture

This commit is contained in:
QLHazyCoder
2026-05-21 07:21:34 +08:00
parent e2485d2e64
commit a7b35ee11a
167 changed files with 9034 additions and 3032 deletions
+1 -2
View File
@@ -106,8 +106,7 @@
return {
activeFlowId: state.activeFlowId,
flowId: state.flowId || state.activeFlowId,
panelMode: state.panelMode,
kiroTargetId: state.kiroTargetId,
targetId: state.targetId,
vpsUrl: state.vpsUrl,
vpsPassword: state.vpsPassword,
customPassword: state.customPassword,
-138
View File
@@ -1,138 +0,0 @@
(function attachBackgroundKiroCredentialArtifact(root, factory) {
root.MultiPageBackgroundKiroCredentialArtifact = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroCredentialArtifactModule(root) {
const kiroStateApi = root?.MultiPageBackgroundKiroState || null;
const FLOW_ID = 'kiro';
const ADAPTER_ID = 'kiro-builder-id';
const ARTIFACT_KIND = 'kiro-builder-id';
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1';
const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs';
const BUILDER_ID_PROFILE_ARN = 'arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX';
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function readKiroRuntime(state = {}) {
return kiroStateApi?.ensureRuntimeState
? kiroStateApi.ensureRuntimeState(state)
: (isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {});
}
function resolveKiroTargetId(state = {}, runtimeState = readKiroRuntime(state)) {
return cleanString(
state?.settingsState?.flows?.kiro?.targetId
|| state?.flows?.kiro?.targetId
|| state?.kiroTargetId
|| runtimeState?.upload?.targetId
|| DEFAULT_TARGET_ID
) || DEFAULT_TARGET_ID;
}
function resolveEmail(state = {}, runtimeState = readKiroRuntime(state)) {
return cleanString(
runtimeState?.register?.email
|| state?.email
|| state?.registrationEmailState?.current
|| state?.accountIdentifier
);
}
function resolveRegion(state = {}, runtimeState = readKiroRuntime(state), targetId = DEFAULT_TARGET_ID) {
return cleanString(
runtimeState?.desktopAuth?.region
|| state?.settingsState?.flows?.kiro?.targets?.[targetId]?.region
|| state?.flows?.kiro?.targets?.[targetId]?.region
|| DEFAULT_REGION
) || DEFAULT_REGION;
}
function assertRequiredField(name, value, message) {
if (!cleanString(value)) {
const error = new Error(message);
error.code = `missing_${name}`;
throw error;
}
}
function redactSecret(value = '') {
const normalized = cleanString(value);
if (!normalized) {
return '';
}
if (normalized.length <= 10) {
return `${normalized.slice(0, 2)}***`;
}
return `${normalized.slice(0, 6)}...${normalized.slice(-4)}`;
}
function buildKiroBuilderIdArtifact(state = {}, options = {}) {
const runtimeState = readKiroRuntime(state);
const desktopAuth = runtimeState?.desktopAuth || {};
const targetId = resolveKiroTargetId(state, runtimeState);
const region = resolveRegion(state, runtimeState, targetId);
const email = resolveEmail(state, runtimeState);
const refreshToken = String(desktopAuth.refreshToken || '');
const clientId = cleanString(desktopAuth.clientId);
const clientSecret = String(desktopAuth.clientSecret || '');
assertRequiredField('refreshToken', refreshToken, '缺少桌面授权 refreshToken,无法提交 Kiro Builder ID 贡献。');
assertRequiredField('clientId', clientId, '缺少桌面授权 clientId,无法提交 Kiro Builder ID 贡献。');
assertRequiredField('clientSecret', clientSecret, '缺少桌面授权 clientSecret,无法提交 Kiro Builder ID 贡献。');
assertRequiredField('email', email, '缺少注册邮箱,无法提交 Kiro Builder ID 贡献。');
return {
schemaVersion: 1,
flow: FLOW_ID,
adapter: ADAPTER_ID,
artifact: ARTIFACT_KIND,
account: {
email,
},
credentials: {
refreshToken,
clientId,
clientSecret,
profileArn: cleanString(options.profileArn) || BUILDER_ID_PROFILE_ARN,
authMethod: 'idc',
region,
authRegion: region,
apiRegion: region,
tokenSource: cleanString(desktopAuth.tokenSource) || 'desktop_authorization_code_pkce',
},
metadata: {
targetId,
authorizedAt: Math.max(0, Number(desktopAuth.authorizedAt) || 0),
generatedAt: new Date().toISOString(),
source: 'flowpilot-extension',
},
};
}
function buildSafeArtifactSummary(artifact = {}) {
return {
flow: cleanString(artifact.flow),
adapter: cleanString(artifact.adapter),
artifact: cleanString(artifact.artifact),
email: cleanString(artifact.account?.email),
clientId: cleanString(artifact.credentials?.clientId),
refreshToken: redactSecret(artifact.credentials?.refreshToken),
clientSecret: redactSecret(artifact.credentials?.clientSecret),
region: cleanString(artifact.credentials?.region),
};
}
return {
ADAPTER_ID,
ARTIFACT_KIND,
BUILDER_ID_PROFILE_ARN,
FLOW_ID,
buildKiroBuilderIdArtifact,
buildSafeArtifactSummary,
redactSecret,
};
});
File diff suppressed because it is too large Load Diff
-196
View File
@@ -1,196 +0,0 @@
(function attachBackgroundKiroDesktopClient(root, factory) {
root.MultiPageBackgroundKiroDesktopClient = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDesktopClientModule() {
const DEFAULT_REGION = 'us-east-1';
const DEFAULT_START_URL = 'https://view.awsapps.com/start';
const DEFAULT_SCOPES = Object.freeze([
'codewhisperer:completions',
'codewhisperer:analysis',
'codewhisperer:conversations',
'codewhisperer:transformations',
'codewhisperer:taskassist',
]);
function cleanString(value = '') {
return String(value ?? '').trim();
}
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
return cleanString(value) || fallback;
}
function buildOidcBaseUrl(region = DEFAULT_REGION) {
return `https://oidc.${normalizeRegion(region)}.amazonaws.com`;
}
async function readResponse(response) {
const text = await response.text();
let json = null;
try {
json = text ? JSON.parse(text) : null;
} catch (_error) {
json = null;
}
return { text, json };
}
async function sha256Bytes(input) {
const encoder = new TextEncoder();
return new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(String(input || ''))));
}
function base64UrlEncode(bytes) {
let binary = '';
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
async function sha256Hex(input) {
const bytes = await sha256Bytes(input);
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, '0')).join('');
}
function randomUrlSafeString(length = 64) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
const size = Math.max(32, Math.floor(Number(length) || 64));
const bytes = new Uint8Array(size);
crypto.getRandomValues(bytes);
let output = '';
for (let index = 0; index < size; index += 1) {
output += alphabet[bytes[index] % alphabet.length];
}
return output;
}
async function generatePkcePair() {
const codeVerifier = randomUrlSafeString(64);
const codeChallenge = base64UrlEncode(await sha256Bytes(codeVerifier));
return {
codeVerifier,
codeChallenge,
};
}
function chooseRedirectPort() {
return 49152 + Math.floor(Math.random() * 16384);
}
function buildRedirectUri(port) {
const normalizedPort = Math.max(1, Math.floor(Number(port) || 0));
if (!normalizedPort) {
throw new Error('缺少桌面授权回调端口。');
}
return `http://127.0.0.1:${normalizedPort}/oauth/callback`;
}
async function registerDesktopClient(params = {}, fetchImpl) {
if (typeof fetchImpl !== 'function') {
throw new Error('registerDesktopClient requires fetch support.');
}
const region = normalizeRegion(params.region);
const oidcBaseUrl = buildOidcBaseUrl(region);
const response = await fetchImpl(`${oidcBaseUrl}/client/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientName: cleanString(params.clientName) || 'Kiro IDE',
clientType: 'public',
scopes: Array.isArray(params.scopes) && params.scopes.length ? params.scopes : DEFAULT_SCOPES,
grantTypes: ['authorization_code', 'refresh_token'],
redirectUris: ['http://127.0.0.1/oauth/callback'],
issuerUrl: cleanString(params.issuerUrl) || DEFAULT_START_URL,
}),
});
const body = await readResponse(response);
if (!response.ok) {
throw new Error(`Kiro 桌面客户端注册失败:${cleanString(body.text || response.statusText) || response.status}`);
}
const clientId = cleanString(body.json?.clientId);
const clientSecret = String(body.json?.clientSecret || '');
if (!clientId || !clientSecret) {
throw new Error('Kiro 桌面客户端注册响应缺少 clientId 或 clientSecret。');
}
return {
region,
clientId,
clientSecret,
clientSecretExpiresAt: Number(body.json?.clientSecretExpiresAt || 0) || 0,
clientIdHash: await sha256Hex(JSON.stringify({
startUrl: cleanString(params.issuerUrl) || DEFAULT_START_URL,
clientId,
})),
};
}
function buildAuthorizeUrl(params = {}) {
const region = normalizeRegion(params.region);
const search = new URLSearchParams();
search.set('response_type', 'code');
search.set('client_id', cleanString(params.clientId));
search.set('redirect_uri', cleanString(params.redirectUri));
search.set('scopes', Array.isArray(params.scopes) && params.scopes.length
? params.scopes.join(',')
: DEFAULT_SCOPES.join(','));
search.set('state', cleanString(params.state));
search.set('code_challenge', cleanString(params.codeChallenge));
search.set('code_challenge_method', 'S256');
return `${buildOidcBaseUrl(region)}/authorize?${search.toString()}`;
}
async function exchangeDesktopAuthorizationCode(params = {}, fetchImpl) {
if (typeof fetchImpl !== 'function') {
throw new Error('exchangeDesktopAuthorizationCode requires fetch support.');
}
const region = normalizeRegion(params.region);
const response = await fetchImpl(`${buildOidcBaseUrl(region)}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId: cleanString(params.clientId),
clientSecret: String(params.clientSecret || ''),
grantType: 'authorization_code',
code: cleanString(params.code),
redirectUri: cleanString(params.redirectUri),
codeVerifier: cleanString(params.codeVerifier),
}),
});
const body = await readResponse(response);
if (!response.ok) {
throw new Error(`Kiro 桌面授权换取 Token 失败:${cleanString(body.text || response.statusText) || response.status}`);
}
const accessToken = String(body.json?.accessToken || '');
const refreshToken = String(body.json?.refreshToken || '');
if (!accessToken || !refreshToken) {
throw new Error('Kiro 桌面授权换取 Token 响应缺少 accessToken 或 refreshToken。');
}
return {
accessToken,
refreshToken,
expiresIn: Number(body.json?.expiresIn || 0) || 0,
tokenType: cleanString(body.json?.tokenType),
region,
};
}
return {
DEFAULT_REGION,
DEFAULT_SCOPES,
DEFAULT_START_URL,
buildAuthorizeUrl,
buildRedirectUri,
chooseRedirectPort,
exchangeDesktopAuthorizationCode,
generatePkcePair,
registerDesktopClient,
};
});
-471
View File
@@ -1,471 +0,0 @@
(function attachBackgroundKiroPublisherKiroRs(root, factory) {
root.MultiPageBackgroundKiroPublisherKiroRs = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroPublisherKiroRsModule(root) {
const kiroStateApi = root?.MultiPageBackgroundKiroState || null;
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1';
const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs';
const BUILDER_ID_PROFILE_ARN = 'arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX';
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((entry) => cloneValue(entry));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
return cleanString(value) || fallback;
}
function normalizeKiroRsApiKey(value = '') {
return cleanString(value);
}
function buildKiroRsAdminHeaders(apiKey = '', extraHeaders = {}) {
const normalizedApiKey = normalizeKiroRsApiKey(apiKey);
return {
...extraHeaders,
...(normalizedApiKey ? {
'x-api-key': normalizedApiKey,
Authorization: `Bearer ${normalizedApiKey}`,
} : {}),
};
}
function readKiroRsResponseMessage(body = {}, fallback = '') {
return cleanString(body?.json?.error?.message || body?.json?.message || body?.text || fallback);
}
function normalizeKiroRsBaseUrl(value = '') {
const normalized = cleanString(value).replace(/\/+$/, '');
if (!normalized) {
throw new Error('缺少 kiro.rs 管理后台地址。');
}
return normalized.endsWith('/admin')
? normalized.slice(0, -'/admin'.length)
: normalized;
}
function normalizeKiroUploadMessage(value = '') {
const rawValue = cleanString(value);
if (!rawValue) {
return '上传成功';
}
const normalizedValue = rawValue.toLowerCase();
if (normalizedValue === 'uploaded' || normalizedValue === 'credential uploaded.') {
return '上传成功';
}
return rawValue;
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error ?? '未知错误');
}
async function readResponse(response) {
const text = await response.text();
let json = null;
try {
json = text ? JSON.parse(text) : null;
} catch (_error) {
json = null;
}
return { text, json };
}
function readKiroRuntime(state = {}) {
return kiroStateApi?.ensureRuntimeState
? kiroStateApi.ensureRuntimeState(state)
: (isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {});
}
function mergeRuntimePatch(currentState = {}, patch = {}) {
return {
kiroRuntime: deepMerge(readKiroRuntime(currentState), patch),
};
}
function resolveKiroTargetId(state = {}) {
return cleanString(
state?.settingsState?.flows?.kiro?.targetId
|| state?.flows?.kiro?.targetId
|| state?.kiroTargetId
|| readKiroRuntime(state).upload?.targetId
|| DEFAULT_TARGET_ID
) || DEFAULT_TARGET_ID;
}
function resolveKiroTargetConfig(state = {}, targetId = DEFAULT_TARGET_ID) {
if (targetId !== DEFAULT_TARGET_ID) {
throw new Error(`暂不支持 Kiro 发布目标:${targetId}`);
}
const nestedConfig = state?.settingsState?.flows?.kiro?.targets?.[targetId]
|| state?.flows?.kiro?.targets?.[targetId]
|| {};
return {
baseUrl: cleanString(nestedConfig.baseUrl || state?.kiroRsUrl),
apiKey: normalizeKiroRsApiKey(nestedConfig.apiKey ?? state?.kiroRsKey ?? ''),
};
}
function buildProxyPayload(state = {}) {
if (!state?.ipProxyEnabled) {
return {};
}
const apiProxyUrl = cleanString(state?.ipProxyApiUrl);
const host = cleanString(state?.ipProxyHost);
const port = cleanString(state?.ipProxyPort);
const protocol = cleanString(state?.ipProxyProtocol) || 'http';
const proxyUrl = apiProxyUrl || (host && port ? `${protocol}://${host}:${port}` : '');
const proxyUsername = cleanString(state?.ipProxyUsername);
const proxyPassword = String(state?.ipProxyPassword || '');
return {
...(proxyUrl ? { proxyUrl } : {}),
...(proxyUsername ? { proxyUsername } : {}),
...(proxyPassword ? { proxyPassword } : {}),
};
}
async function sha256Hex(input = '') {
const encoder = new TextEncoder();
const bytes = encoder.encode(String(input ?? ''));
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
return Array.from(new Uint8Array(digest))
.map((value) => value.toString(16).padStart(2, '0'))
.join('');
}
async function buildMachineId(refreshToken = '') {
const normalizedRefreshToken = cleanString(refreshToken);
if (!normalizedRefreshToken) {
throw new Error('缺少 refreshToken,无法生成 machineId。');
}
return sha256Hex(`KotlinNativeAPI/${normalizedRefreshToken}`);
}
function buildUploadPayload(state = {}) {
const runtimeState = readKiroRuntime(state);
const targetId = resolveKiroTargetId(state);
const desktopAuth = runtimeState.desktopAuth || {};
const register = runtimeState.register || {};
const refreshToken = String(desktopAuth.refreshToken || '');
const clientId = cleanString(desktopAuth.clientId);
const clientSecret = String(desktopAuth.clientSecret || '');
const region = normalizeRegion(
desktopAuth.region
|| state?.settingsState?.flows?.kiro?.targets?.[targetId]?.region
|| state?.flows?.kiro?.targets?.[targetId]?.region
|| DEFAULT_REGION
);
const email = cleanString(register.email || state?.email);
if (!refreshToken) {
throw new Error('缺少桌面授权 refreshToken,请先完成步骤 8。');
}
if (!clientId || !clientSecret) {
throw new Error('缺少桌面授权 clientId 或 clientSecret,请先完成步骤 7-8。');
}
if (!email) {
throw new Error('缺少注册邮箱,无法上传到 kiro.rs。');
}
return {
targetId,
region,
email,
refreshToken,
profileArn: BUILDER_ID_PROFILE_ARN,
clientId,
clientSecret,
authMethod: 'idc',
authRegion: region,
apiRegion: region,
...buildProxyPayload(state),
};
}
async function checkKiroRsConnection(baseUrl, apiKey, fetchImpl) {
const normalizedBaseUrl = normalizeKiroRsBaseUrl(baseUrl);
const normalizedApiKey = normalizeKiroRsApiKey(apiKey);
const response = await fetchImpl(`${normalizedBaseUrl}/api/admin/credentials`, {
method: 'GET',
headers: buildKiroRsAdminHeaders(normalizedApiKey, {
Accept: 'application/json',
}),
});
const body = await readResponse(response);
const detail = readKiroRsResponseMessage(body, response.statusText);
if (response.ok) {
return {
ok: true,
status: response.status,
message: `kiro.rs 连接正常(HTTP ${response.status}`,
};
}
if (response.status === 405) {
return {
ok: true,
status: response.status,
message: 'kiro.rs 上传接口可访问。',
};
}
if (response.status === 401 || response.status === 403) {
return {
ok: false,
status: response.status,
message: `kiro.rs API Key 被拒绝(HTTP ${response.status}${detail ? `${detail}` : ''}`,
};
}
if (response.status === 404) {
return {
ok: false,
status: response.status,
message: `未找到 kiro.rs 管理接口(HTTP 404${detail ? `${detail}` : ''}`,
};
}
return {
ok: false,
status: response.status,
message: detail || `kiro.rs 连接失败(HTTP ${response.status}`,
};
}
async function uploadBuilderIdCredential(baseUrl, apiKey, payload, fetchImpl) {
const normalizedBaseUrl = normalizeKiroRsBaseUrl(baseUrl);
const normalizedApiKey = normalizeKiroRsApiKey(apiKey);
const response = await fetchImpl(`${normalizedBaseUrl}/api/admin/credentials`, {
method: 'POST',
headers: buildKiroRsAdminHeaders(normalizedApiKey, {
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: JSON.stringify(payload),
});
const body = await readResponse(response);
if (!response.ok) {
const message = readKiroRsResponseMessage(body, response.statusText) || `HTTP ${response.status}`;
throw new Error(`kiro.rs 凭据上传失败:${message}`);
}
return {
credentialId: Number(body.json?.credentialId || body.json?.credential_id || 0) || null,
email: cleanString(body.json?.email),
message: normalizeKiroUploadMessage(body.json?.message),
raw: body.json,
};
}
function createKiroRsPublisher(deps = {}) {
const {
addLog = async () => {},
completeNodeFromBackground,
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getState = async () => ({}),
maybeSubmitFlowContribution = async () => ({ ok: true, skipped: true, reason: 'not_configured' }),
setState = async () => {},
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Kiro kiro.rs publisher requires completeNodeFromBackground.');
}
if (typeof fetchImpl !== 'function') {
throw new Error('Kiro kiro.rs publisher requires fetch support.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function applyRuntimeState(currentState = {}, patch = {}) {
const nextPatch = mergeRuntimePatch(currentState, patch);
await setState(nextPatch);
return nextPatch;
}
async function persistFailure(currentState = {}, message = '') {
const nextPatch = mergeRuntimePatch(currentState, {
session: {
currentStage: 'upload',
lastError: message,
},
upload: {
status: 'error',
error: message,
},
});
await setState(nextPatch);
}
function shouldUseContributionUpload(state = {}) {
return Boolean(state?.accountContributionEnabled)
&& cleanString(state?.activeFlowId || state?.flowId).toLowerCase() === 'kiro'
&& cleanString(state?.contributionAdapterId).toLowerCase() === 'kiro-builder-id';
}
async function executeKiroUploadCredential(state = {}) {
const nodeId = String(state?.nodeId || 'kiro-upload-credential').trim();
const currentState = await getState();
try {
if (shouldUseContributionUpload(currentState)) {
await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
lastError: '',
lastWarning: '',
},
upload: {
targetId: 'contribution',
status: 'uploading',
error: '',
},
});
await log('步骤 9:正在上传 Builder ID 到贡献池...', 'info', nodeId);
const contributionResult = await maybeSubmitFlowContribution(currentState, {
nodeId,
trigger: 'kiro-step-9',
});
if (!contributionResult?.ok || contributionResult?.skipped) {
throw new Error(contributionResult?.message || 'Kiro 贡献上传失败。');
}
const uploadedAt = Date.now();
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
lastError: '',
},
upload: {
targetId: 'contribution',
status: 'uploaded',
error: '',
credentialId: contributionResult.contributionId || '',
lastMessage: contributionResult.message || '贡献上传成功',
lastUploadedAt: uploadedAt,
},
});
await log(`步骤 9:贡献上传完成,状态:${contributionResult.message || '贡献上传成功'}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
return;
}
const targetId = resolveKiroTargetId(currentState);
const targetConfig = resolveKiroTargetConfig(currentState, targetId);
const baseUrl = normalizeKiroRsBaseUrl(targetConfig.baseUrl);
const apiKey = String(targetConfig.apiKey || '');
if (!apiKey) {
throw new Error('缺少 kiro.rs API Key。');
}
const uploadInput = buildUploadPayload(currentState);
const machineId = await buildMachineId(uploadInput.refreshToken);
await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
lastError: '',
lastWarning: '',
},
upload: {
targetId,
status: 'uploading',
error: '',
},
});
await log('步骤 9:正在上传 Builder ID 凭据到 kiro.rs...', 'info', nodeId);
const connection = await checkKiroRsConnection(baseUrl, apiKey, fetchImpl);
if (!connection.ok) {
throw new Error(connection.message);
}
const uploadResult = await uploadBuilderIdCredential(baseUrl, apiKey, {
refreshToken: uploadInput.refreshToken,
profileArn: uploadInput.profileArn,
authMethod: uploadInput.authMethod,
clientId: uploadInput.clientId,
clientSecret: uploadInput.clientSecret,
region: uploadInput.region,
authRegion: uploadInput.authRegion,
apiRegion: uploadInput.apiRegion,
machineId,
email: uploadInput.email,
...(uploadInput.proxyUrl ? { proxyUrl: uploadInput.proxyUrl } : {}),
...(uploadInput.proxyUsername ? { proxyUsername: uploadInput.proxyUsername } : {}),
...(uploadInput.proxyPassword ? { proxyPassword: uploadInput.proxyPassword } : {}),
}, fetchImpl);
const uploadedAt = Date.now();
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'upload',
lastError: '',
},
upload: {
targetId,
status: 'uploaded',
error: '',
credentialId: uploadResult.credentialId,
lastMessage: uploadResult.message || '上传成功',
lastUploadedAt: uploadedAt,
},
});
await log(`步骤 9:kiro.rs 上传完成,状态:${uploadResult.message || '上传成功'}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
} catch (error) {
const message = getErrorMessage(error);
await persistFailure(currentState, message);
throw error;
}
}
return {
executeKiroUploadCredential,
};
}
return {
buildKiroRsPayload: buildUploadPayload,
buildMachineId,
checkKiroRsConnection,
createKiroRsPublisher,
normalizeKiroRsBaseUrl,
normalizeKiroUploadMessage,
uploadBuilderIdCredential,
};
});
File diff suppressed because it is too large Load Diff
-356
View File
@@ -1,356 +0,0 @@
(function attachBackgroundKiroState(root, factory) {
root.MultiPageBackgroundKiroState = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroStateModule() {
const DEFAULT_TARGET_ID = 'kiro-rs';
const DEFAULT_REGION = 'us-east-1';
const FLAT_FIELD_DEFINITIONS = Object.freeze([]);
const FLAT_FIELD_KEYS = Object.freeze([]);
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((entry) => cloneValue(entry));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function normalizeString(value = '', fallback = '') {
const normalized = String(value ?? '').trim();
return normalized || fallback;
}
function normalizeInteger(value, fallback = 0) {
const numeric = Math.floor(Number(value));
return Number.isInteger(numeric) ? numeric : fallback;
}
function normalizeNullableInteger(value, fallback = null) {
if (value === null || value === undefined || value === '') {
return fallback;
}
const numeric = Math.floor(Number(value));
return Number.isInteger(numeric) ? numeric : fallback;
}
function normalizeBoolean(value, fallback = false) {
if (value === true || value === false) {
return value;
}
return fallback;
}
function buildDefaultRuntimeState() {
return {
session: {
currentStage: '',
registerTabId: null,
desktopTabId: null,
startedAt: 0,
pageState: '',
pageUrl: '',
lastError: '',
lastWarning: '',
},
register: {
email: '',
fullName: '',
verificationRequestedAt: 0,
loginUrl: '',
status: '',
completedAt: 0,
},
webAuth: {
status: '',
completedAt: 0,
hasAccessToken: false,
hasSessionToken: false,
},
desktopAuth: {
region: DEFAULT_REGION,
clientId: '',
clientSecret: '',
clientIdHash: '',
state: '',
codeVerifier: '',
codeChallenge: '',
redirectUri: '',
redirectPort: 0,
authorizeUrl: '',
authorizationCode: '',
accessToken: '',
refreshToken: '',
status: '',
authorizedAt: 0,
otpRequestedAt: 0,
tokenSource: 'desktop_authorization_code_pkce',
},
upload: {
targetId: DEFAULT_TARGET_ID,
status: '',
error: '',
credentialId: null,
lastMessage: '',
lastUploadedAt: 0,
},
};
}
function normalizeRuntimeState(runtimeState = {}) {
const merged = deepMerge(buildDefaultRuntimeState(), runtimeState);
return {
session: {
currentStage: normalizeString(merged.session?.currentStage),
registerTabId: normalizeNullableInteger(merged.session?.registerTabId),
desktopTabId: normalizeNullableInteger(merged.session?.desktopTabId),
startedAt: Math.max(0, normalizeInteger(merged.session?.startedAt)),
pageState: normalizeString(merged.session?.pageState),
pageUrl: normalizeString(merged.session?.pageUrl),
lastError: normalizeString(merged.session?.lastError),
lastWarning: normalizeString(merged.session?.lastWarning),
},
register: {
email: normalizeString(merged.register?.email),
fullName: normalizeString(merged.register?.fullName),
verificationRequestedAt: Math.max(0, normalizeInteger(merged.register?.verificationRequestedAt)),
loginUrl: normalizeString(merged.register?.loginUrl),
status: normalizeString(merged.register?.status),
completedAt: Math.max(0, normalizeInteger(merged.register?.completedAt)),
},
webAuth: {
status: normalizeString(merged.webAuth?.status),
completedAt: Math.max(0, normalizeInteger(merged.webAuth?.completedAt)),
hasAccessToken: normalizeBoolean(merged.webAuth?.hasAccessToken),
hasSessionToken: normalizeBoolean(merged.webAuth?.hasSessionToken),
},
desktopAuth: {
region: normalizeString(merged.desktopAuth?.region, DEFAULT_REGION),
clientId: normalizeString(merged.desktopAuth?.clientId),
clientSecret: normalizeString(merged.desktopAuth?.clientSecret),
clientIdHash: normalizeString(merged.desktopAuth?.clientIdHash),
state: normalizeString(merged.desktopAuth?.state),
codeVerifier: normalizeString(merged.desktopAuth?.codeVerifier),
codeChallenge: normalizeString(merged.desktopAuth?.codeChallenge),
redirectUri: normalizeString(merged.desktopAuth?.redirectUri),
redirectPort: Math.max(0, normalizeInteger(merged.desktopAuth?.redirectPort)),
authorizeUrl: normalizeString(merged.desktopAuth?.authorizeUrl),
authorizationCode: normalizeString(merged.desktopAuth?.authorizationCode),
accessToken: normalizeString(merged.desktopAuth?.accessToken),
refreshToken: normalizeString(merged.desktopAuth?.refreshToken),
status: normalizeString(merged.desktopAuth?.status),
authorizedAt: Math.max(0, normalizeInteger(merged.desktopAuth?.authorizedAt)),
otpRequestedAt: Math.max(0, normalizeInteger(merged.desktopAuth?.otpRequestedAt)),
tokenSource: normalizeString(
merged.desktopAuth?.tokenSource,
'desktop_authorization_code_pkce'
),
},
upload: {
targetId: normalizeString(merged.upload?.targetId, DEFAULT_TARGET_ID),
status: normalizeString(merged.upload?.status),
error: normalizeString(merged.upload?.error),
credentialId: normalizeNullableInteger(merged.upload?.credentialId),
lastMessage: normalizeString(merged.upload?.lastMessage),
lastUploadedAt: Math.max(0, normalizeInteger(merged.upload?.lastUploadedAt)),
},
};
}
function ensureRuntimeState(state = {}) {
return normalizeRuntimeState(isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {});
}
function projectRuntimeFields() {
return {};
}
function buildStateView(state = {}) {
return {
...state,
kiroRuntime: ensureRuntimeState(state),
};
}
function buildSessionStatePatch(currentState = {}, updates = {}) {
if (!isPlainObject(updates?.kiroRuntime)) {
return {};
}
const nextRuntimeState = normalizeRuntimeState(
deepMerge(ensureRuntimeState(currentState), updates.kiroRuntime)
);
return {
kiroRuntime: nextRuntimeState,
};
}
function buildRuntimeResetPatch(currentState = {}, patch = {}) {
return {
kiroRuntime: normalizeRuntimeState(
deepMerge(ensureRuntimeState(currentState), patch)
),
};
}
function buildStartRegisterResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = buildDefaultRuntimeState();
nextRuntimeState.upload.targetId = currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID;
return {
kiroRuntime: nextRuntimeState,
};
}
function buildRegisterOnlyResetPatch(currentState = {}, registerPatch = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = normalizeRuntimeState({
...buildDefaultRuntimeState(),
session: {
...currentRuntimeState.session,
currentStage: 'register',
desktopTabId: null,
pageState: '',
pageUrl: '',
lastError: '',
lastWarning: '',
},
register: {
...currentRuntimeState.register,
completedAt: 0,
status: '',
...registerPatch,
},
upload: {
...buildDefaultRuntimeState().upload,
targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID,
},
});
return {
kiroRuntime: nextRuntimeState,
};
}
function buildDesktopResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
return {
kiroRuntime: normalizeRuntimeState({
...currentRuntimeState,
session: {
...currentRuntimeState.session,
currentStage: 'desktop-authorize',
desktopTabId: null,
pageState: '',
pageUrl: '',
lastError: '',
lastWarning: '',
},
desktopAuth: buildDefaultRuntimeState().desktopAuth,
upload: {
...buildDefaultRuntimeState().upload,
targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID,
},
}),
};
}
function buildUploadResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
return {
kiroRuntime: normalizeRuntimeState({
...currentRuntimeState,
upload: {
...buildDefaultRuntimeState().upload,
targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID,
},
}),
};
}
function buildDownstreamResetPatch(stepKey = '', currentState = {}) {
switch (normalizeString(stepKey)) {
case 'kiro-open-register-page':
return buildStartRegisterResetPatch(currentState);
case 'kiro-submit-email':
return buildRegisterOnlyResetPatch(currentState, {
email: '',
fullName: '',
verificationRequestedAt: 0,
});
case 'kiro-submit-name':
return buildRegisterOnlyResetPatch(currentState, {
fullName: '',
verificationRequestedAt: 0,
});
case 'kiro-submit-verification-code':
return buildRegisterOnlyResetPatch(currentState, {});
case 'kiro-submit-password':
return buildRegisterOnlyResetPatch(currentState, {});
case 'kiro-complete-register-consent':
return buildDesktopResetPatch(currentState);
case 'kiro-start-desktop-authorize':
return buildDesktopResetPatch(currentState);
case 'kiro-complete-desktop-authorize':
return buildUploadResetPatch(currentState);
case 'kiro-upload-credential':
return buildUploadResetPatch(currentState);
default:
return {};
}
}
function applyNodeCompletionPayload(currentState = {}, payload = {}) {
return buildSessionStatePatch(currentState, payload);
}
function buildFreshKeepState(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = buildDefaultRuntimeState();
nextRuntimeState.upload.targetId = currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID;
return {
kiroRuntime: nextRuntimeState,
...(Object.prototype.hasOwnProperty.call(currentState, 'kiroTargetId')
? { kiroTargetId: normalizeString(currentState.kiroTargetId, DEFAULT_TARGET_ID).toLowerCase() }
: {}),
};
}
return {
DEFAULT_REGION,
DEFAULT_TARGET_ID,
FLAT_FIELD_DEFINITIONS,
FLAT_FIELD_KEYS,
applyNodeCompletionPayload,
buildDefaultRuntimeState,
buildDownstreamResetPatch,
buildFreshKeepState,
buildSessionStatePatch,
buildStateView,
ensureRuntimeState,
projectRuntimeFields,
};
});
-1
View File
@@ -22,7 +22,6 @@
'openai-auth': '认证页',
'gmail-mail': 'Gmail 邮箱',
'sidepanel': '侧边栏',
'signup-page': '认证页',
'vps-panel': 'CPA 面板',
'sub2api-panel': 'SUB2API 后台',
'codex2api-panel': 'Codex2API 后台',
+17 -25
View File
@@ -91,7 +91,7 @@
}
return capabilityRegistry.validateAutoRunStart({
activeFlowId: options?.activeFlowId ?? validationState?.activeFlowId,
panelMode: options?.panelMode ?? validationState?.panelMode,
targetId: options?.targetId ?? validationState?.targetId,
signupMethod: options?.signupMethod ?? validationState?.signupMethod,
state: validationState,
});
@@ -113,7 +113,7 @@
return capabilityRegistry.validateModeSwitch({
activeFlowId: options?.activeFlowId ?? validationState?.activeFlowId,
changedKeys: options?.changedKeys,
panelMode: options?.panelMode ?? validationState?.panelMode,
targetId: options?.targetId ?? validationState?.targetId,
signupMethod: options?.signupMethod ?? validationState?.signupMethod,
state: validationState,
});
@@ -217,10 +217,6 @@
return String(targetId || fallbackSourceId).trim().toLowerCase() || fallbackSourceId;
}
function mapAutoRunTargetIdToPanelMode(targetId = '', fallback = 'cpa') {
return String(targetId || fallback || 'cpa').trim().toLowerCase() || 'cpa';
}
function buildAutoRunFlowStateUpdates(payload = {}) {
const hasActiveFlowId = Object.prototype.hasOwnProperty.call(payload, 'activeFlowId');
const hasTargetId = Object.prototype.hasOwnProperty.call(payload, 'targetId');
@@ -233,11 +229,11 @@
flowId: activeFlowId,
};
if (hasTargetId) {
if (activeFlowId === 'kiro') {
updates.kiroTargetId = normalizeMessageTargetId('kiro', payload.targetId, 'kiro-rs');
} else {
updates.panelMode = mapAutoRunTargetIdToPanelMode(payload.targetId, 'cpa');
}
updates.targetId = normalizeMessageTargetId(
activeFlowId,
payload.targetId,
activeFlowId === 'kiro' ? 'kiro-rs' : 'cpa'
);
}
return updates;
}
@@ -297,10 +293,10 @@
}
const signupTabId = typeof getTabId === 'function'
? await getTabId('signup-page')
? await getTabId('openai-auth')
: null;
const signupTabAlive = signupTabId && typeof isTabAlive === 'function'
? await isTabAlive('signup-page')
? await isTabAlive('openai-auth')
: Boolean(signupTabId);
if (!signupTabId || !signupTabAlive) {
@@ -1309,7 +1305,7 @@
const state = await getState();
const autoRunStartValidation = validateAutoRunStart(state, {
activeFlowId: autoRunFlowStateUpdates.activeFlowId ?? state?.activeFlowId,
panelMode: autoRunFlowStateUpdates.panelMode ?? state?.panelMode,
targetId: autoRunFlowStateUpdates.targetId ?? state?.targetId,
state,
});
if (autoRunStartValidation?.ok === false) {
@@ -1352,7 +1348,7 @@
const state = await getState();
const autoRunStartValidation = validateAutoRunStart(state, {
activeFlowId: autoRunFlowStateUpdates.activeFlowId ?? state?.activeFlowId,
panelMode: autoRunFlowStateUpdates.panelMode ?? state?.panelMode,
targetId: autoRunFlowStateUpdates.targetId ?? state?.targetId,
state,
});
if (autoRunStartValidation?.ok === false) {
@@ -1451,7 +1447,7 @@
Object.prototype.hasOwnProperty.call(updates, 'phoneVerificationEnabled')
|| Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|| Object.prototype.hasOwnProperty.call(updates, 'signupMethod')
|| Object.prototype.hasOwnProperty.call(updates, 'panelMode')
|| Object.prototype.hasOwnProperty.call(updates, 'targetId')
|| Object.prototype.hasOwnProperty.call(updates, 'activeFlowId')
|| Object.prototype.hasOwnProperty.call(updates, 'accountContributionEnabled')
) {
@@ -1600,10 +1596,8 @@
);
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth',
stateUpdates.panelMode
?? currentState?.panelMode
?? stateUpdates.openaiIntegrationTargetId
?? currentState?.openaiIntegrationTargetId
stateUpdates.targetId
?? currentState?.targetId
?? 'cpa'
);
await addLog(
@@ -1620,10 +1614,8 @@
} else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) {
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth',
stateUpdates.panelMode
?? currentState?.panelMode
?? stateUpdates.openaiIntegrationTargetId
?? currentState?.openaiIntegrationTargetId
stateUpdates.targetId
?? currentState?.targetId
?? 'cpa'
);
await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info');
@@ -1661,7 +1653,7 @@
);
const targetId = normalizeMessageTargetId(
activeFlowId,
message.payload?.targetId || currentState?.kiroTargetId || 'kiro-rs',
message.payload?.targetId || currentState?.targetId || 'kiro-rs',
'kiro-rs'
);
const nestedTargetConfig = currentState?.settingsState?.flows?.kiro?.targets?.[targetId]
+2 -3
View File
@@ -42,10 +42,10 @@
}
function getPanelMode(state = {}) {
if (state.panelMode === 'sub2api') {
if (state.targetId === 'sub2api') {
return 'sub2api';
}
if (state.panelMode === 'codex2api') {
if (state.targetId === 'codex2api') {
return 'codex2api';
}
return 'cpa';
@@ -128,7 +128,6 @@
switch (source) {
case 'openai-auth':
case 'signup-page':
return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
case 'duck-mail':
return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/');
+10 -10
View File
@@ -4585,7 +4585,7 @@
visibleStep,
logStepKey: 'phone-verification',
});
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'STEP8_GET_STATE',
source: 'background',
payload: { visibleStep },
@@ -4676,7 +4676,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: '提交添加手机号' })
: 30000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'SUBMIT_PHONE_NUMBER',
source: 'background',
payload: {
@@ -4723,7 +4723,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交手机验证码' })
: 45000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'SUBMIT_PHONE_VERIFICATION_CODE',
source: 'background',
payload: {
@@ -4756,7 +4756,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: 'resend phone verification code' })
: 65000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'RESEND_PHONE_VERIFICATION_CODE',
source: 'background',
payload: options || {},
@@ -4780,7 +4780,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交注册手机验证码' })
: 45000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'SUBMIT_PHONE_VERIFICATION_CODE',
step: visibleStep,
source: 'background',
@@ -4809,7 +4809,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送注册手机验证码' })
: 65000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'RESEND_VERIFICATION_CODE',
step: visibleStep,
source: 'background',
@@ -4834,7 +4834,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: 'return to add-phone page' })
: 30000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'RETURN_TO_ADD_PHONE',
source: 'background',
payload: {},
@@ -4863,7 +4863,7 @@
}
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
try {
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'CHECK_PHONE_RESEND_ERROR',
source: 'background',
payload: { visibleStep },
@@ -6135,7 +6135,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交登录手机验证码' })
: 45000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'SUBMIT_PHONE_VERIFICATION_CODE',
step: visibleStep,
source: 'background',
@@ -6164,7 +6164,7 @@
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送登录手机验证码' })
: 65000;
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'RESEND_VERIFICATION_CODE',
step: visibleStep,
source: 'background',
-509
View File
@@ -1,509 +0,0 @@
(function attachBackgroundRuntimeState(root, factory) {
root.MultiPageBackgroundRuntimeState = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundRuntimeStateModule() {
function createRuntimeStateHelpers(deps = {}) {
const {
DEFAULT_ACTIVE_FLOW_ID = 'openai',
defaultNodeStatuses = {},
} = deps;
const RUNTIME_SHARED_FIELDS = Object.freeze([
'automationWindowId',
'tabRegistry',
'sourceLastUrls',
'flowStartTime',
]);
const RUNTIME_PROXY_FIELDS = Object.freeze([
'ipProxyApiPool',
'ipProxyApiCurrentIndex',
'ipProxyApiCurrent',
'ipProxyAccountPool',
'ipProxyAccountCurrentIndex',
'ipProxyAccountCurrent',
'ipProxyPool',
'ipProxyCurrentIndex',
'ipProxyCurrent',
]);
const OPENAI_FLOW_FIELD_GROUPS = Object.freeze({
auth: Object.freeze([
'oauthUrl',
'localhostUrl',
]),
platformBinding: Object.freeze([
'cpaOAuthState',
'cpaManagementOrigin',
'sub2apiSessionId',
'sub2apiOAuthState',
'sub2apiGroupId',
'sub2apiDraftName',
'sub2apiProxyId',
'sub2apiGroupIds',
'codex2apiSessionId',
'codex2apiOAuthState',
]),
plus: Object.freeze([
'plusCheckoutTabId',
'plusCheckoutUrl',
'plusCheckoutCountry',
'plusCheckoutCurrency',
'plusCheckoutSource',
'plusBillingCountryText',
'plusBillingAddress',
'plusPaypalApprovedAt',
'plusGoPayApprovedAt',
'plusReturnUrl',
'plusManualConfirmationPending',
'plusManualConfirmationRequestId',
'plusManualConfirmationStep',
'plusManualConfirmationMethod',
'plusManualConfirmationTitle',
'plusManualConfirmationMessage',
'gopayHelperReferenceId',
'gopayHelperGoPayGuid',
'gopayHelperRedirectUrl',
'gopayHelperNextAction',
'gopayHelperFlowId',
'gopayHelperChallengeId',
'gopayHelperStartPayload',
'gopayHelperTaskId',
'gopayHelperTaskStatus',
'gopayHelperStatusText',
'gopayHelperRemoteStage',
'gopayHelperApiWaitingFor',
'gopayHelperApiInputDeadlineAt',
'gopayHelperApiInputWaitSeconds',
'gopayHelperLastInputError',
'gopayHelperOtpInvalidCount',
'gopayHelperFailureStage',
'gopayHelperFailureDetail',
'gopayHelperTaskPayload',
'gopayHelperOrderCreatedAt',
'gopayHelperTaskProgressSignature',
'gopayHelperTaskProgressAt',
'gopayHelperTaskProgressTaskId',
'gopayHelperPinPayload',
'gopayHelperResolvedOtp',
'gopayHelperOtpRequestId',
'gopayHelperOtpReferenceId',
]),
phoneVerification: Object.freeze([
'currentPhoneActivation',
'phoneNumber',
'currentPhoneVerificationCode',
'currentPhoneVerificationCountdownEndsAt',
'currentPhoneVerificationCountdownWindowIndex',
'currentPhoneVerificationCountdownWindowTotal',
'reusablePhoneActivation',
'freeReusablePhoneActivation',
'phoneReusableActivationPool',
'signupPhoneNumber',
'signupPhoneActivation',
'signupPhoneCompletedActivation',
'signupPhoneVerificationRequestedAt',
'signupPhoneVerificationPurpose',
]),
luckmail: Object.freeze([
'currentLuckmailPurchase',
'currentLuckmailMailCursor',
'currentYydsMailInbox',
]),
identity: Object.freeze([
'resolvedSignupMethod',
'accountIdentifierType',
'accountIdentifier',
'registrationEmailState',
'email',
'password',
'lastEmailTimestamp',
'lastSignupCode',
'lastLoginCode',
'step8VerificationTargetEmail',
]),
});
const FLOW_FIELD_GROUPS = Object.freeze({
openai: OPENAI_FLOW_FIELD_GROUPS,
});
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((item) => cloneValue(item));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function normalizePlainObject(value) {
return isPlainObject(value) ? value : {};
}
function normalizeFlowId(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || DEFAULT_ACTIVE_FLOW_ID;
}
function normalizeRunId(value = '') {
return String(value || '').trim();
}
function normalizeNodeStatus(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (!normalized) {
return 'pending';
}
return normalized;
}
function buildDefaultNodeStatuses() {
return Object.fromEntries(
Object.entries(normalizePlainObject(defaultNodeStatuses)).map(([key, value]) => [
String(key),
normalizeNodeStatus(value),
])
);
}
function normalizeNodeStatuses(value) {
const base = buildDefaultNodeStatuses();
if (!isPlainObject(value)) {
return base;
}
const next = { ...base };
for (const [key, status] of Object.entries(value)) {
const nodeId = String(key || '').trim();
if (!nodeId) continue;
next[nodeId] = normalizeNodeStatus(status);
}
return next;
}
function pickDefinedFields(state = {}, fields = []) {
const next = {};
for (const field of fields) {
if (Object.prototype.hasOwnProperty.call(state, field)) {
next[field] = cloneValue(state[field]);
}
}
return next;
}
function buildSharedState(baseValue = {}, state = {}) {
return {
...cloneValue(normalizePlainObject(baseValue)),
...pickDefinedFields(state, RUNTIME_SHARED_FIELDS),
};
}
function buildServiceState(baseValue = {}, state = {}) {
const base = cloneValue(normalizePlainObject(baseValue));
return {
...base,
proxy: {
...cloneValue(normalizePlainObject(base.proxy)),
...pickDefinedFields(state, RUNTIME_PROXY_FIELDS),
},
};
}
function buildScopedFlowState(baseFlowState = {}, state = {}, flowId = 'openai') {
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId]));
const scopedState = {
...baseScopedState,
};
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
scopedState[groupKey] = {
...cloneValue(normalizePlainObject(baseScopedState[groupKey])),
...pickDefinedFields(state, fields),
};
}
return scopedState;
}
function buildFlowState(baseValue = {}, state = {}) {
const baseFlowState = cloneValue(normalizePlainObject(baseValue));
return {
...baseFlowState,
openai: buildScopedFlowState(baseFlowState, state, 'openai'),
};
}
function listFlowFieldNames() {
const fields = [];
for (const flowGroups of Object.values(FLOW_FIELD_GROUPS)) {
for (const groupFields of Object.values(normalizePlainObject(flowGroups))) {
for (const field of Array.isArray(groupFields) ? groupFields : []) {
const normalizedField = String(field || '').trim();
if (normalizedField) {
fields.push(normalizedField);
}
}
}
}
return Array.from(new Set(fields));
}
const FLOW_RUNTIME_FIELDS = Object.freeze(listFlowFieldNames());
const RUNTIME_TOP_LEVEL_FIELDS = Object.freeze([
...RUNTIME_SHARED_FIELDS,
...RUNTIME_PROXY_FIELDS,
...FLOW_RUNTIME_FIELDS,
]);
const RUNTIME_TOP_LEVEL_FIELD_SET = new Set(RUNTIME_TOP_LEVEL_FIELDS);
const RUNTIME_PATCH_IGNORED_KEYS = new Set([
'runtimeState',
'flowState',
'sharedState',
'serviceState',
'flows',
'shared',
'services',
'currentStep',
'stepStatuses',
'legacyStepCompat',
'flowId',
'runId',
'activeFlowId',
'activeRunId',
'currentNodeId',
'nodeStatuses',
...RUNTIME_TOP_LEVEL_FIELDS,
]);
function projectScopedFlowFields(flowState = {}) {
const next = {};
for (const [flowId, fieldGroups] of Object.entries(FLOW_FIELD_GROUPS)) {
const scopedState = normalizePlainObject(normalizePlainObject(flowState)[flowId]);
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
const group = normalizePlainObject(scopedState[groupKey]);
Object.assign(next, pickDefinedFields(group, fields));
}
}
return next;
}
function projectRuntimeViewFields(runtimeState = {}) {
const normalizedRuntimeState = normalizePlainObject(runtimeState);
return {
...pickDefinedFields(
normalizePlainObject(normalizedRuntimeState.sharedState),
RUNTIME_SHARED_FIELDS
),
...pickDefinedFields(
normalizePlainObject(normalizePlainObject(normalizedRuntimeState.serviceState).proxy),
RUNTIME_PROXY_FIELDS
),
...projectScopedFlowFields(normalizedRuntimeState.flowState),
};
}
function buildRuntimeInputFromPatch(updates = {}) {
const normalizedUpdates = normalizePlainObject(updates);
const runtimeStatePatch = normalizePlainObject(normalizedUpdates.runtimeState);
const next = {
...pickDefinedFields(normalizedUpdates, [
'flowId',
'runId',
'activeFlowId',
'activeRunId',
'currentNodeId',
'nodeStatuses',
]),
...pickDefinedFields(runtimeStatePatch, [
'flowId',
'runId',
'activeFlowId',
'activeRunId',
'currentNodeId',
'nodeStatuses',
]),
...projectRuntimeViewFields(runtimeStatePatch),
...pickDefinedFields(normalizedUpdates, RUNTIME_TOP_LEVEL_FIELDS),
};
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'sharedState')) {
Object.assign(
next,
pickDefinedFields(normalizePlainObject(normalizedUpdates.sharedState), RUNTIME_SHARED_FIELDS)
);
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'serviceState')) {
Object.assign(
next,
pickDefinedFields(
normalizePlainObject(normalizePlainObject(normalizedUpdates.serviceState).proxy),
RUNTIME_PROXY_FIELDS
)
);
}
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flowState')) {
Object.assign(next, projectScopedFlowFields(normalizedUpdates.flowState));
}
if (!Object.prototype.hasOwnProperty.call(next, 'flowId') && Object.prototype.hasOwnProperty.call(next, 'activeFlowId')) {
next.flowId = next.activeFlowId;
}
if (!Object.prototype.hasOwnProperty.call(next, 'runId') && Object.prototype.hasOwnProperty.call(next, 'activeRunId')) {
next.runId = next.activeRunId;
}
return next;
}
function buildRuntimeStateDefault() {
return {
flowId: DEFAULT_ACTIVE_FLOW_ID,
runId: '',
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
activeRunId: '',
currentNodeId: '',
nodeStatuses: {},
sharedState: {},
serviceState: {
proxy: {},
},
flowState: {
openai: {
auth: {},
platformBinding: {},
plus: {},
phoneVerification: {},
luckmail: {},
identity: {},
},
},
};
}
function ensureRuntimeState(state = {}) {
const baseRuntimeState = {
...buildRuntimeStateDefault(),
...cloneValue(normalizePlainObject(state.runtimeState)),
};
const activeFlowId = normalizeFlowId(
Object.prototype.hasOwnProperty.call(state, 'activeFlowId')
? state.activeFlowId
: Object.prototype.hasOwnProperty.call(state, 'flowId')
? state.flowId
: Object.prototype.hasOwnProperty.call(baseRuntimeState, 'activeFlowId')
? baseRuntimeState.activeFlowId
: baseRuntimeState.flowId
);
const currentNodeId = String(
Object.prototype.hasOwnProperty.call(state, 'currentNodeId')
? state.currentNodeId
: baseRuntimeState.currentNodeId
).trim();
const nodeStatuses = normalizeNodeStatuses(
Object.prototype.hasOwnProperty.call(state, 'nodeStatuses')
? state.nodeStatuses
: baseRuntimeState.nodeStatuses
);
return {
...baseRuntimeState,
flowId: activeFlowId,
activeFlowId,
runId: normalizeRunId(
Object.prototype.hasOwnProperty.call(state, 'runId')
? state.runId
: Object.prototype.hasOwnProperty.call(state, 'activeRunId')
? state.activeRunId
: Object.prototype.hasOwnProperty.call(baseRuntimeState, 'runId')
? baseRuntimeState.runId
: baseRuntimeState.activeRunId
),
activeRunId: normalizeRunId(
Object.prototype.hasOwnProperty.call(state, 'runId')
? state.runId
: Object.prototype.hasOwnProperty.call(state, 'activeRunId')
? state.activeRunId
: Object.prototype.hasOwnProperty.call(baseRuntimeState, 'runId')
? baseRuntimeState.runId
: baseRuntimeState.activeRunId
),
currentNodeId,
nodeStatuses,
sharedState: buildSharedState(baseRuntimeState.sharedState, state),
serviceState: buildServiceState(baseRuntimeState.serviceState, state),
flowState: buildFlowState(baseRuntimeState.flowState, state),
};
}
function buildPersistentPatchPayload(updates = {}) {
const next = {};
for (const [key, value] of Object.entries(normalizePlainObject(updates))) {
if (RUNTIME_PATCH_IGNORED_KEYS.has(key)) {
continue;
}
next[key] = cloneValue(value);
}
return next;
}
function buildStateView(state = {}) {
const runtimeState = ensureRuntimeState(state);
return {
...state,
...projectRuntimeViewFields(runtimeState),
flowId: runtimeState.flowId,
runId: runtimeState.runId,
activeFlowId: runtimeState.activeFlowId,
activeRunId: runtimeState.activeRunId,
currentNodeId: runtimeState.currentNodeId,
nodeStatuses: cloneValue(runtimeState.nodeStatuses),
flowState: cloneValue(runtimeState.flowState),
sharedState: cloneValue(runtimeState.sharedState),
serviceState: cloneValue(runtimeState.serviceState),
flows: cloneValue(runtimeState.flowState),
shared: cloneValue(runtimeState.sharedState),
services: cloneValue(runtimeState.serviceState),
runtimeState,
};
}
function buildSessionStatePatch(currentState = {}, updates = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const runtimeState = ensureRuntimeState({
runtimeState: currentRuntimeState,
...projectRuntimeViewFields(currentRuntimeState),
...buildRuntimeInputFromPatch(updates),
});
return {
...buildPersistentPatchPayload(updates),
flowId: runtimeState.flowId,
runId: runtimeState.runId,
activeFlowId: runtimeState.activeFlowId,
activeRunId: runtimeState.activeRunId,
currentNodeId: runtimeState.currentNodeId,
nodeStatuses: cloneValue(runtimeState.nodeStatuses),
runtimeState,
};
}
return {
DEFAULT_ACTIVE_FLOW_ID,
FLOW_FIELD_GROUPS,
OPENAI_FLOW_FIELD_GROUPS,
RUNTIME_PROXY_FIELDS,
RUNTIME_SHARED_FIELDS,
buildDefaultRuntimeState: buildRuntimeStateDefault,
buildSessionStatePatch,
buildStateView,
ensureRuntimeState,
};
}
return {
createRuntimeStateHelpers,
};
});
+16 -16
View File
@@ -26,7 +26,7 @@
setEmailState,
setState,
SIGNUP_ENTRY_URL,
SIGNUP_PAGE_INJECT_FILES,
OPENAI_AUTH_INJECT_FILES,
waitForTabStableComplete = null,
waitForTabUrlMatch,
} = deps;
@@ -57,16 +57,16 @@
}
async function openSignupEntryTab(step = 1) {
const tabId = await reuseOrCreateTab('signup-page', SIGNUP_ENTRY_URL, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
const tabId = await reuseOrCreateTab('openai-auth', SIGNUP_ENTRY_URL, {
inject: OPENAI_AUTH_INJECT_FILES,
injectSource: 'openai-auth',
});
await waitForSignupEntryTabToSettle(tabId, step);
await ensureContentScriptReadyOnTab('signup-page', tabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
await ensureContentScriptReadyOnTab('openai-auth', tabId, {
inject: OPENAI_AUTH_INJECT_FILES,
injectSource: 'openai-auth',
timeoutMs: 45000,
retryDelayMs: 900,
logMessage: `步骤 ${step}:ChatGPT 官网仍在加载,正在重试连接内容脚本...`,
@@ -77,7 +77,7 @@
async function ensureSignupEntryPageReady(step = 1) {
const tabId = await openSignupEntryTab(step);
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'ENSURE_SIGNUP_ENTRY_READY',
step,
source: 'background',
@@ -186,9 +186,9 @@
}
}
await ensureContentScriptReadyOnTab('signup-page', tabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
await ensureContentScriptReadyOnTab('openai-auth', tabId, {
inject: OPENAI_AUTH_INJECT_FILES,
injectSource: 'openai-auth',
timeoutMs: 45000,
retryDelayMs: 900,
logMessage: landingState === 'password_page'
@@ -204,7 +204,7 @@
};
}
const result = await sendToContentScriptResilient('signup-page', {
const result = await sendToContentScriptResilient('openai-auth', {
type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY',
step,
source: 'background',
@@ -244,9 +244,9 @@
throw new Error(`认证页面标签页已关闭,无法完成步骤 ${step} 的提交后确认。`);
}
await ensureContentScriptReadyOnTab('signup-page', tabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
await ensureContentScriptReadyOnTab('openai-auth', tabId, {
inject: OPENAI_AUTH_INJECT_FILES,
injectSource: 'openai-auth',
timeoutMs: 45000,
retryDelayMs: 900,
logMessage: `步骤 ${step}:认证页仍在切换,正在等待页面恢复后继续确认提交流程...`,
@@ -254,7 +254,7 @@
let result;
try {
result = await sendToContentScriptResilient('signup-page', {
result = await sendToContentScriptResilient('openai-auth', {
type: 'PREPARE_SIGNUP_VERIFICATION',
step,
source: 'background',
-352
View File
@@ -1,352 +0,0 @@
(function attachBackgroundStep9(root, factory) {
root.MultiPageBackgroundStep9 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep9Module() {
function createStep9Executor(deps = {}) {
const {
addLog,
chrome,
cleanupStep8NavigationListeners,
clickWithDebugger,
completeNodeFromBackground,
ensureStep8SignupPageReady,
getOAuthFlowRemainingMs,
getOAuthFlowStepTimeoutMs,
getStep8CallbackUrlFromNavigation,
getStep8CallbackUrlFromTabUpdate,
getStep8EffectLabel,
getTabId,
isTabAlive,
prepareStep8DebuggerClick,
recoverOAuthLocalhostTimeout,
reloadStep8ConsentPage,
reuseOrCreateTab,
sleepWithStop,
STEP8_CLICK_RETRY_DELAY_MS,
STEP8_MAX_ROUNDS,
STEP8_READY_WAIT_TIMEOUT_MS,
STEP8_STRATEGIES,
throwIfStep8SettledOrStopped,
triggerStep8ContentStrategy,
waitForStep8ClickEffect,
waitForStep8Ready,
setWebNavListener,
setWebNavCommittedListener,
setStep8PendingReject,
setStep8TabUpdatedListener,
shouldDeferStep9CallbackTimeout,
} = deps;
const LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS = 240000;
const CALLBACK_TIMEOUT_CHECK_INTERVAL_MS = 1000;
function getVisibleStep(state, fallback = 9) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : fallback;
}
function getAuthLoginStepForVisibleStep(visibleStep) {
return visibleStep >= 12 ? 10 : 7;
}
function addStepLog(step, message, level = 'info') {
return addLog(message, level, { step, stepKey: 'confirm-oauth' });
}
async function executeStep9(state) {
const visibleStep = getVisibleStep(state, 9);
let activeState = state;
if (!activeState.oauthUrl) {
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}`);
}
await addStepLog(visibleStep, '正在监听 localhost 回调地址...');
let callbackTimeoutMs = LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
let timeoutRecoveryAttempted = false;
while (true) {
try {
callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS, {
step: visibleStep,
actionLabel: 'OAuth localhost 回调',
oauthUrl: activeState?.oauthUrl || '',
})
: LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
break;
} catch (error) {
if (timeoutRecoveryAttempted || typeof recoverOAuthLocalhostTimeout !== 'function') {
throw error;
}
const recoveredState = await recoverOAuthLocalhostTimeout({
error,
state: activeState,
visibleStep,
});
if (!recoveredState) {
throw error;
}
activeState = recoveredState;
timeoutRecoveryAttempted = true;
}
}
return new Promise((resolve, reject) => {
let resolved = false;
let signupTabId = null;
const callbackWaitStartedAt = Date.now();
let timeoutCheckTimer = null;
let timeoutDeferredLogged = false;
const cleanupListener = () => {
if (timeoutCheckTimer) {
clearTimeout(timeoutCheckTimer);
timeoutCheckTimer = null;
}
cleanupStep8NavigationListeners();
setStep8PendingReject(null);
};
const rejectStep9 = (error) => {
if (resolved) return;
resolved = true;
cleanupListener();
reject(error);
};
const finalizeStep9Callback = (callbackUrl) => {
if (resolved || !callbackUrl) return;
resolved = true;
cleanupListener();
addStepLog(visibleStep, `已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
return completeNodeFromBackground(state?.nodeId || 'confirm-oauth', { localhostUrl: callbackUrl });
}).then(() => {
resolve();
}).catch((err) => {
reject(err);
});
};
const isCallbackTimeoutDeferred = async (elapsedMs) => {
if (typeof shouldDeferStep9CallbackTimeout !== 'function') {
return false;
}
try {
const deferred = await shouldDeferStep9CallbackTimeout({
tabId: signupTabId,
visibleStep,
elapsedMs,
oauthUrl: activeState?.oauthUrl || '',
});
if (deferred && !timeoutDeferredLogged) {
timeoutDeferredLogged = true;
await addStepLog(
visibleStep,
'检测到认证页仍在安全验证/授权跳转中,暂停本地回调超时判定,继续等待 localhost 回调...',
'info'
);
}
return Boolean(deferred);
} catch (error) {
await addStepLog(
visibleStep,
`复核认证页跳转状态失败(${error?.message || error}),继续按原超时规则等待回调。`,
'warn'
);
return false;
}
};
const checkCallbackTimeout = async () => {
if (resolved) {
return;
}
const elapsedMs = Date.now() - callbackWaitStartedAt;
if (await isCallbackTimeoutDeferred(elapsedMs)) {
timeoutCheckTimer = setTimeout(checkCallbackTimeout, CALLBACK_TIMEOUT_CHECK_INTERVAL_MS);
return;
}
if (elapsedMs >= LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS) {
rejectStep9(new Error(`${Math.round(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
return;
}
if (typeof getOAuthFlowRemainingMs === 'function') {
try {
await getOAuthFlowRemainingMs({
step: visibleStep,
actionLabel: 'OAuth localhost 回调',
oauthUrl: activeState?.oauthUrl || '',
});
} catch (error) {
rejectStep9(error);
return;
}
} else if (elapsedMs >= callbackTimeoutMs) {
rejectStep9(new Error(`${Math.round(callbackTimeoutMs / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
return;
}
timeoutCheckTimer = setTimeout(checkCallbackTimeout, CALLBACK_TIMEOUT_CHECK_INTERVAL_MS);
};
timeoutCheckTimer = setTimeout(
checkCallbackTimeout,
Math.min(CALLBACK_TIMEOUT_CHECK_INTERVAL_MS, Math.max(1, callbackTimeoutMs))
);
setStep8PendingReject((error) => {
rejectStep9(error);
});
setWebNavListener((details) => {
const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
finalizeStep9Callback(callbackUrl);
});
setWebNavCommittedListener((details) => {
const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
finalizeStep9Callback(callbackUrl);
});
setStep8TabUpdatedListener((tabId, changeInfo, tab) => {
const callbackUrl = getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId);
finalizeStep9Callback(callbackUrl);
});
(async () => {
try {
throwIfStep8SettledOrStopped(resolved);
signupTabId = await getTabId('signup-page');
throwIfStep8SettledOrStopped(resolved);
if (signupTabId && await isTabAlive('signup-page')) {
await chrome.tabs.update(signupTabId, { active: true });
await addStepLog(visibleStep, '已切回认证页,正在准备调试器点击...');
} else {
signupTabId = await reuseOrCreateTab('signup-page', activeState.oauthUrl);
await addStepLog(visibleStep, '已重新打开认证页,正在准备调试器点击...');
}
throwIfStep8SettledOrStopped(resolved);
chrome.webNavigation.onBeforeNavigate.addListener(deps.getWebNavListener());
chrome.webNavigation.onCommitted.addListener(deps.getWebNavCommittedListener());
chrome.tabs.onUpdated.addListener(deps.getStep8TabUpdatedListener());
await ensureStep8SignupPageReady(signupTabId, {
timeoutMs: typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: visibleStep,
actionLabel: '等待 OAuth 同意页内容脚本就绪',
})
: 15000,
visibleStep,
logStepKey: 'confirm-oauth',
logMessage: '认证页内容脚本尚未就绪,正在等待页面恢复...',
});
for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) {
throwIfStep8SettledOrStopped(resolved);
const pageState = await waitForStep8Ready(
signupTabId,
typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(STEP8_READY_WAIT_TIMEOUT_MS, {
step: visibleStep,
actionLabel: '等待 OAuth 同意页出现',
})
: STEP8_READY_WAIT_TIMEOUT_MS,
{ visibleStep }
);
if (!pageState?.consentReady) {
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
continue;
}
const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)];
await addStepLog(visibleStep, `${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label}...`);
if (strategy.mode === 'debugger') {
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: visibleStep,
actionLabel: '定位 OAuth 同意页继续按钮',
})
: 15000;
const clickTarget = await prepareStep8DebuggerClick(signupTabId, {
timeoutMs: clickActionTimeoutMs,
responseTimeoutMs: clickActionTimeoutMs,
visibleStep,
});
throwIfStep8SettledOrStopped(resolved);
await clickWithDebugger(signupTabId, clickTarget?.rect, { visibleStep });
} else {
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: visibleStep,
actionLabel: '点击 OAuth 同意页继续按钮',
})
: 15000;
await triggerStep8ContentStrategy(signupTabId, strategy.strategy, {
timeoutMs: clickActionTimeoutMs,
responseTimeoutMs: clickActionTimeoutMs,
visibleStep,
});
}
if (resolved) {
return;
}
const effect = await waitForStep8ClickEffect(
signupTabId,
pageState.url,
typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: visibleStep,
actionLabel: '等待 OAuth 同意页点击生效',
})
: 15000,
{ visibleStep }
);
if (resolved) {
return;
}
if (effect.progressed) {
await addStepLog(visibleStep, `检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
break;
}
if (round >= STEP8_MAX_ROUNDS) {
throw new Error(`步骤 ${visibleStep}:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
}
await addStepLog(visibleStep, `${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS}...`, 'warn');
await reloadStep8ConsentPage(
signupTabId,
typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(30000, {
step: visibleStep,
actionLabel: '刷新 OAuth 同意页',
})
: 30000,
{ visibleStep }
);
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
}
} catch (err) {
rejectStep9(err);
}
})();
});
}
return { executeStep9 };
}
return { createStep9Executor };
});
-276
View File
@@ -1,276 +0,0 @@
(function attachBackgroundCpaSessionImport(root, factory) {
root.MultiPageBackgroundCpaSessionImport = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundCpaSessionImportModule() {
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
function createCpaSessionImportExecutor(deps = {}) {
const {
addLog: rawAddLog = async () => {},
chrome,
completeNodeFromBackground,
ensureContentScriptReadyOnTabUntilStopped,
getTabId,
isTabAlive,
registerTab,
sendTabMessageUntilStopped,
sleepWithStop = async () => {},
throwIfStopped = () => {},
waitForTabCompleteUntilStopped = async () => {},
} = deps;
let cpaApi = null;
function addStepLog(step, message, level = 'info') {
return rawAddLog(message, level, {
step,
stepKey: 'cpa-session-import',
});
}
function getCpaApi() {
if (cpaApi) {
return cpaApi;
}
const factory = deps.createCpaApi
|| self.MultiPageBackgroundCpaApi?.createCpaApi;
if (typeof factory !== 'function') {
throw new Error('CPA 接口模块未加载,无法导入当前 ChatGPT 会话。');
}
cpaApi = factory({
addLog: rawAddLog,
});
return cpaApi;
}
function normalizeString(value = '') {
return String(value || '').trim();
}
function resolveVisibleStep(state = {}) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : 10;
}
function isSupportedChatGptSessionUrl(url = '') {
try {
const parsed = new URL(String(url || ''));
if (!/^https?:$/i.test(parsed.protocol)) {
return false;
}
const hostname = String(parsed.hostname || '').trim().toLowerCase();
return /(^|\.)chatgpt\.com$/.test(hostname)
|| hostname === 'chat.openai.com'
|| /(^|\.)openai\.com$/.test(hostname);
} catch {
return false;
}
}
function getSessionTabHostPriority(url = '') {
try {
const hostname = String(new URL(String(url || '')).hostname || '').trim().toLowerCase();
if (/(^|\.)chatgpt\.com$/.test(hostname)) {
return 0;
}
if (hostname === 'chat.openai.com') {
return 1;
}
if (/(^|\.)openai\.com$/.test(hostname)) {
return 2;
}
} catch {
return Number.POSITIVE_INFINITY;
}
return Number.POSITIVE_INFINITY;
}
function getSessionTabActivityPriority(tab = {}) {
if (tab?.active && tab?.currentWindow) {
return 0;
}
if (tab?.active) {
return 1;
}
return 2;
}
function pickPreferredSessionTab(tabs = []) {
const candidates = (Array.isArray(tabs) ? tabs : [])
.filter((tab) => Number.isInteger(tab?.id) && isSupportedChatGptSessionUrl(tab.url));
if (!candidates.length) {
return null;
}
return candidates.reduce((best, candidate) => {
if (!best) {
return candidate;
}
const candidateHostPriority = getSessionTabHostPriority(candidate.url);
const bestHostPriority = getSessionTabHostPriority(best.url);
if (candidateHostPriority !== bestHostPriority) {
return candidateHostPriority < bestHostPriority ? candidate : best;
}
const candidateActivityPriority = getSessionTabActivityPriority(candidate);
const bestActivityPriority = getSessionTabActivityPriority(best);
if (candidateActivityPriority !== bestActivityPriority) {
return candidateActivityPriority < bestActivityPriority ? candidate : best;
}
const candidateLastAccessed = Number(candidate?.lastAccessed) || 0;
const bestLastAccessed = Number(best?.lastAccessed) || 0;
if (candidateLastAccessed !== bestLastAccessed) {
return candidateLastAccessed > bestLastAccessed ? candidate : best;
}
return Number(candidate.id) < Number(best.id) ? candidate : best;
}, null);
}
async function readSupportedSessionTab(tabId) {
const numericTabId = Number(tabId) || 0;
if (!numericTabId || !chrome?.tabs?.get) {
return null;
}
const tab = await chrome.tabs.get(numericTabId).catch(() => null);
return tab?.id && isSupportedChatGptSessionUrl(tab.url)
? tab
: null;
}
async function findFallbackSessionTab() {
if (!chrome?.tabs?.query) {
return null;
}
const activeTabs = await chrome.tabs.query({ active: true, currentWindow: true }).catch(() => []);
const activeMatch = pickPreferredSessionTab(activeTabs);
const allTabs = await chrome.tabs.query({}).catch(() => []);
const globalMatch = pickPreferredSessionTab(allTabs);
return pickPreferredSessionTab([activeMatch, globalMatch]);
}
async function resolveSessionTabId(state = {}) {
const registeredTabId = typeof getTabId === 'function'
? await getTabId(PLUS_CHECKOUT_SOURCE)
: null;
if (registeredTabId && typeof isTabAlive === 'function' && await isTabAlive(PLUS_CHECKOUT_SOURCE)) {
const registeredTab = await readSupportedSessionTab(registeredTabId);
if (registeredTab?.id) {
return registeredTab.id;
}
}
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
const storedTab = await readSupportedSessionTab(storedTabId);
if (storedTab?.id) {
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, storedTab.id);
}
return storedTab.id;
}
const fallbackTab = await findFallbackSessionTab();
if (fallbackTab?.id) {
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, fallbackTab.id);
}
return fallbackTab.id;
}
throw new Error('未找到可读取 ChatGPT 会话的标签页,请先打开一个已登录的 ChatGPT / OpenAI 页面,或完成当前 Plus 支付链路。');
}
async function getResolvedSessionTab(tabId, visibleStep) {
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
if (!tab?.id) {
throw new Error(`步骤 ${visibleStep}:ChatGPT 会话标签页不存在或已关闭,无法继续导入 CPA。`);
}
if (!isSupportedChatGptSessionUrl(tab.url)) {
throw new Error(`步骤 ${visibleStep}:当前标签页不在 ChatGPT / OpenAI 页面,无法读取当前登录会话。`);
}
return tab;
}
async function readCurrentChatGptSession(tabId, visibleStep) {
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 会话页完成加载,再继续读取当前登录会话...`,
});
const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'PLUS_CHECKOUT_GET_STATE',
source: 'background',
payload: {
includeSession: true,
includeAccessToken: true,
},
});
if (sessionResult?.error) {
throw new Error(sessionResult.error);
}
const session = sessionResult?.session && typeof sessionResult.session === 'object' && !Array.isArray(sessionResult.session)
? sessionResult.session
: null;
const accessToken = normalizeString(
sessionResult?.accessToken
|| session?.accessToken
);
if (!session && !accessToken) {
throw new Error(`步骤 ${visibleStep}:未读取到有效的 ChatGPT 会话或 accessToken,请确认当前标签页仍处于已登录状态。`);
}
return {
session,
accessToken,
};
}
async function executeCpaSessionImport(state = {}) {
throwIfStopped();
const visibleStep = resolveVisibleStep(state);
const api = getCpaApi();
await addStepLog(visibleStep, '正在定位当前 ChatGPT 会话页并准备导入 CPA...', 'info');
const tabId = await resolveSessionTabId(state);
const tab = await getResolvedSessionTab(tabId, visibleStep);
if (chrome?.tabs?.update) {
await chrome.tabs.update(tab.id, { active: true }).catch(() => {});
}
await addStepLog(visibleStep, '正在读取当前 ChatGPT 登录会话...', 'info');
const sessionState = await readCurrentChatGptSession(tab.id, visibleStep);
throwIfStopped();
const result = await api.importCurrentChatGptSession({
...state,
session: sessionState.session,
accessToken: sessionState.accessToken,
}, {
visibleStep,
logLabel: `步骤 ${visibleStep}`,
logOptions: { step: visibleStep, stepKey: 'cpa-session-import' },
timeoutMs: 120000,
importTimeoutMs: 120000,
});
await completeNodeFromBackground(state?.nodeId || 'cpa-session-import', result);
}
return {
executeCpaSessionImport,
isSupportedChatGptSessionUrl,
};
}
return {
createCpaSessionImportExecutor,
};
});
File diff suppressed because it is too large Load Diff
-962
View File
@@ -1,962 +0,0 @@
(function attachBackgroundStep8(root, factory) {
root.MultiPageBackgroundStep8 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
function createStep8Executor(deps = {}) {
const {
addLog: rawAddLog = async () => {},
chrome,
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
CLOUD_MAIL_PROVIDER = 'cloudmail',
completeNodeFromBackground,
confirmCustomVerificationStepBypass,
ensureMail2925MailboxSession,
ensureIcloudMailSession,
ensureStep8VerificationPageReady,
getOAuthFlowRemainingMs,
getOAuthFlowStepTimeoutMs,
getMailConfig,
getState,
getTabId,
HOTMAIL_PROVIDER,
isTabAlive,
isVerificationMailPollingError,
LUCKMAIL_PROVIDER,
resolveSignupEmailForFlow,
resolveVerificationStep,
rerunStep7ForStep8Recovery,
reuseOrCreateTab,
sendToContentScriptResilient,
persistRegistrationEmailState = null,
phoneVerificationHelpers = null,
setState,
shouldUseCustomRegistrationEmail,
sleepWithStop,
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS,
throwIfStopped,
} = deps;
let activeFetchLoginCodeStep = null;
let activeFetchLoginCodeStepKey = 'fetch-login-code';
function normalizeLogStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function normalizeStepLogMessage(message) {
return String(message || '')
.replace(/^步骤\s*\d+\s*[:]\s*/, '')
.replace(/^Step\s+\d+\s*[:]\s*/i, '')
.trim();
}
function addLog(message, level = 'info', options = {}) {
const normalizedOptions = options && typeof options === 'object' ? { ...options } : {};
const step = normalizeLogStep(normalizedOptions.step || normalizedOptions.visibleStep)
|| normalizeLogStep(activeFetchLoginCodeStep);
if (step) {
normalizedOptions.step = step;
if (!normalizedOptions.stepKey) {
normalizedOptions.stepKey = activeFetchLoginCodeStepKey || 'fetch-login-code';
}
}
delete normalizedOptions.visibleStep;
return rawAddLog(normalizeStepLogMessage(message), level, normalizedOptions);
}
function getVisibleStep(state, fallback = 8) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : fallback;
}
function normalizeSignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function normalizeIdentifierType(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'phone' || normalized === 'email' ? normalized : '';
}
function isPhoneLoginCodeMode(state = {}) {
if (normalizeIdentifierType(state?.accountIdentifierType) === 'phone') {
return true;
}
return normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone'
&& Boolean(
String(state?.signupPhoneNumber || '').trim()
|| String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim()
|| String(state?.signupPhoneActivation?.phoneNumber || '').trim()
);
}
function getAuthLoginStepForVisibleStep(visibleStep) {
return visibleStep >= 11 ? 10 : 7;
}
async function getStep8ReadyTimeoutMs(actionLabel, expectedOauthUrl = '', visibleStep = 8) {
if (typeof getOAuthFlowStepTimeoutMs !== 'function') {
return 15000;
}
return getOAuthFlowStepTimeoutMs(15000, {
step: visibleStep,
actionLabel,
oauthUrl: expectedOauthUrl,
});
}
function getStep8RemainingTimeResolver(expectedOauthUrl = '', visibleStep = 8) {
if (typeof getOAuthFlowRemainingMs !== 'function') {
return undefined;
}
return async (details = {}) => getOAuthFlowRemainingMs({
step: visibleStep,
actionLabel: details.actionLabel || '登录验证码流程',
oauthUrl: expectedOauthUrl,
});
}
function normalizeStep8VerificationTargetEmail(value) {
return String(value || '').trim().toLowerCase();
}
function resolveBoundEmailLoginTarget(state = {}, visibleStep = 0) {
const email = String(
state?.step8VerificationTargetEmail
|| state?.email
|| state?.registrationEmailState?.current
|| ''
).trim();
if (!email) {
throw new Error(`步骤 ${visibleStep || 0}:缺少绑定邮箱,无法使用邮箱模式重新发起 OAuth 登录。`);
}
return email;
}
function buildBoundEmailLoginState(state = {}, visibleStep = 0) {
const email = resolveBoundEmailLoginTarget(state, visibleStep);
return {
...state,
forceLoginIdentifierType: 'email',
forceEmailLogin: true,
signupMethod: 'email',
resolvedSignupMethod: 'email',
accountIdentifierType: 'email',
accountIdentifier: email,
email,
step8VerificationTargetEmail: normalizeStep8VerificationTargetEmail(email),
};
}
async function getLoginAuthStateFromContent(visibleStep, options = {}) {
if (typeof sendToContentScriptResilient !== 'function') {
return {};
}
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 15000);
const result = await sendToContentScriptResilient(
'signup-page',
{
type: 'GET_LOGIN_AUTH_STATE',
source: 'background',
payload: {},
},
{
timeoutMs,
responseTimeoutMs: timeoutMs,
retryDelayMs: 600,
logMessage: options.logMessage || `步骤 ${visibleStep}:认证页正在切换,等待页面重新就绪...`,
logStep: visibleStep,
logStepKey: options.logStepKey || activeFetchLoginCodeStepKey || 'fetch-login-code',
}
);
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function submitAddEmailIfNeeded(state, visibleStep, initialPageState = null) {
if (typeof resolveSignupEmailForFlow !== 'function' || typeof sendToContentScriptResilient !== 'function') {
return { state, pageState: initialPageState };
}
const pageState = initialPageState?.state
? initialPageState
: await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: 15000,
logMessage: `步骤 ${visibleStep}:正在确认是否已进入添加邮箱页...`,
});
if (pageState?.state !== 'add_email_page') {
return { state, pageState };
}
const latestState = typeof getState === 'function' ? await getState() : state;
const resolvedEmail = await resolveSignupEmailForFlow(latestState, {
preserveAccountIdentity: true,
});
await addLog(`步骤 ${visibleStep}:检测到添加邮箱页,正在添加邮箱 ${resolvedEmail} 并进入邮箱验证码页...`);
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(60000, {
step: visibleStep,
actionLabel: '添加邮箱并进入验证码页',
oauthUrl: latestState?.oauthUrl || state?.oauthUrl || '',
})
: 60000;
const result = await sendToContentScriptResilient(
'signup-page',
{
type: 'SUBMIT_ADD_EMAIL',
source: 'background',
payload: {
email: resolvedEmail,
nodeId: state?.nodeId || activeFetchLoginCodeStepKey || 'fetch-login-code',
},
},
{
timeoutMs,
responseTimeoutMs: timeoutMs,
retryDelayMs: 700,
logMessage: `步骤 ${visibleStep}:添加邮箱页面正在切换,等待邮箱验证码页就绪...`,
logStep: visibleStep,
logStepKey: activeFetchLoginCodeStepKey || 'fetch-login-code',
}
);
if (result?.error) {
throw new Error(result.error);
}
const displayedEmail = normalizeStep8VerificationTargetEmail(result?.displayedEmail || resolvedEmail);
let persistedState = latestState;
if (typeof persistRegistrationEmailState === 'function') {
await persistRegistrationEmailState(latestState, resolvedEmail, {
source: activeFetchLoginCodeStepKey === 'bind-email' ? 'bind_email' : 'step8_add_email',
preserveAccountIdentity: true,
});
persistedState = typeof getState === 'function' ? await getState() : latestState;
} else {
await setState({
email: resolvedEmail,
step8VerificationTargetEmail: displayedEmail,
});
persistedState = {
...latestState,
email: resolvedEmail,
step8VerificationTargetEmail: displayedEmail,
};
}
return {
state: {
...persistedState,
email: resolvedEmail,
step8VerificationTargetEmail: displayedEmail,
},
pageState: {
state: result?.directOAuthConsentPage ? 'oauth_consent_page' : 'verification_page',
displayedEmail,
url: result?.url || pageState?.url || '',
},
};
}
async function completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, options = {}) {
await setState({
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
const fromRecovery = Boolean(options.fromRecovery);
const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code';
await addLog(
`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页${fromRecovery ? '(轮询失败后复核)' : ''},跳过登录验证码拉取并继续后续流程。`,
'warn',
{ step: visibleStep, stepKey }
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
loginVerificationRequestedAt: null,
skipLoginVerificationStep: true,
directOAuthConsentPage: true,
});
}
}
async function completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState = {}, options = {}) {
await setState({
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code';
await addLog(
`步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`,
'warn',
{ step: visibleStep, stepKey }
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
loginVerificationRequestedAt: null,
skipLoginVerificationStep: true,
addPhonePage: pageState?.state === 'add_phone_page' || Boolean(pageState?.addPhonePage),
phoneVerificationPage: pageState?.state === 'phone_verification_page' || Boolean(pageState?.phoneVerificationPage),
});
}
}
async function completeStep8WhenDeferredToBindEmail(visibleStep, options = {}) {
await setState({
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
await addLog(
`步骤 ${visibleStep}:当前认证页已进入添加邮箱页,跳过登录短信验证码,交给后续“绑定邮箱”步骤处理。`,
'warn'
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
loginVerificationRequestedAt: null,
skipLoginVerificationStep: true,
addEmailPage: true,
});
}
}
function isStep8AddPhoneStateError(error) {
const message = String(error?.message || error || '');
return /add-phone|手机号页面|手机号验证页|phone[\s-_]verification|phone\s+number/i.test(message);
}
async function recoverStep8PollingFailure(currentState, visibleStep) {
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
try {
const pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep,
allowPhoneVerificationPage: true,
allowAddEmailPage: true,
timeoutMs: await getStep8ReadyTimeoutMs(
'登录验证码轮询异常后复核认证页状态',
currentState?.oauthUrl || '',
visibleStep
),
});
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { fromRecovery: true, nodeId: currentState?.nodeId });
return { outcome: 'completed' };
}
if (pageState?.state === 'verification_page' || pageState?.state === 'phone_verification_page' || pageState?.state === 'add_email_page') {
await addLog(
`步骤 ${visibleStep}:检测到邮箱轮询/页面通信异常,但认证页仍在当前登录后续页面,先在当前链路重试,不回到步骤 ${authLoginStep}`,
'warn'
);
return { outcome: 'retry_without_step7' };
}
} catch (inspectError) {
if (isStep8RestartStep7Error(inspectError)) {
return { outcome: 'restart_step7', error: inspectError };
}
if (isStep8AddPhoneStateError(inspectError)) {
throw inspectError;
}
await addLog(
`步骤 ${visibleStep}:轮询失败后复核认证页状态异常:${inspectError?.message || inspectError},将回到步骤 ${authLoginStep} 重试。`,
'warn'
);
}
return { outcome: 'restart_step7' };
}
function getExpectedMail2925MailboxEmail(state = {}) {
if (Boolean(state?.mail2925UseAccountPool)) {
const currentAccountId = String(state?.currentMail2925AccountId || '').trim();
const accounts = Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
const currentAccount = accounts.find((account) => String(account?.id || '') === currentAccountId) || null;
const accountEmail = String(currentAccount?.email || '').trim().toLowerCase();
if (accountEmail) {
return accountEmail;
}
}
return String(state?.mail2925BaseEmail || '').trim().toLowerCase();
}
async function focusOrOpenMailTab(mail) {
const alive = await isTabAlive(mail.source);
if (alive) {
if (mail.navigateOnReuse) {
await reuseOrCreateTab(mail.source, mail.url, {
inject: mail.inject,
injectSource: mail.injectSource,
});
return;
}
const tabId = await getTabId(mail.source);
await chrome.tabs.update(tabId, { active: true });
return;
}
await reuseOrCreateTab(mail.source, mail.url, {
inject: mail.inject,
injectSource: mail.injectSource,
});
}
function getStep8ResendIntervalMs(state = {}) {
const mail = getMailConfig(state);
if (mail?.provider === LUCKMAIL_PROVIDER) {
return 15000;
}
if (mail?.provider === HOTMAIL_PROVIDER || mail?.provider === '2925') {
return 0;
}
return Math.max(0, Number(STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS) || 0);
}
async function executeLoginPhoneCodeStep(state, signupTabId, visibleStep) {
if (!Number.isInteger(signupTabId)) {
throw new Error(`步骤 ${visibleStep}:认证页面标签页已关闭,无法继续手机号登录验证码流程。`);
}
if (typeof phoneVerificationHelpers?.completeLoginPhoneVerificationFlow !== 'function') {
throw new Error(`步骤 ${visibleStep}:手机号登录验证码流程不可用,接码模块尚未初始化。`);
}
const result = await phoneVerificationHelpers.completeLoginPhoneVerificationFlow(signupTabId, {
state,
visibleStep,
});
await completeNodeFromBackground(state?.nodeId || 'fetch-login-code', {
phoneVerification: true,
loginPhoneVerification: true,
code: result?.code || '',
});
return result || {};
}
async function ensureAuthTabForPostLoginStep(state, visibleStep) {
const authTabId = await getTabId('signup-page');
if (authTabId) {
await chrome.tabs.update(authTabId, { active: true });
return authTabId;
}
if (!state?.oauthUrl) {
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成刷新 OAuth 并登录。`);
}
return reuseOrCreateTab('signup-page', state.oauthUrl);
}
async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) {
const stepKey = options.stepKey || 'post-login-phone-verification';
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', {
step: visibleStep,
stepKey,
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'post-login-phone-verification', {
directOAuthConsentPage: true,
phoneVerification: false,
});
}
}
async function executePostLoginPhoneVerification(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 9);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = runtime.stepKey || 'post-login-phone-verification';
const authTabId = await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认手机号验证页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认是否需要手机号验证...`,
logStepKey: activeFetchLoginCodeStepKey,
});
if (pageState?.state === 'oauth_consent_page') {
await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, {
nodeId: state?.nodeId || runtime.fallbackNodeId,
stepKey: activeFetchLoginCodeStepKey,
});
return;
}
if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') {
throw new Error(`步骤 ${visibleStep}:手机号验证步骤只处理添加手机号页或手机验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
if (!state?.phoneVerificationEnabled) {
throw new Error(`步骤 ${visibleStep}:检测到需要手机号验证,但手机接码未开启。URL: ${pageState?.url || ''}`.trim());
}
if (typeof phoneVerificationHelpers?.completePhoneVerificationFlow !== 'function') {
throw new Error(`步骤 ${visibleStep}:手机号验证流程不可用,接码模块尚未初始化。`);
}
const result = await phoneVerificationHelpers.completePhoneVerificationFlow(authTabId, pageState, {
step: visibleStep,
visibleStep,
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || runtime.fallbackNodeId || 'post-login-phone-verification', {
phoneVerification: true,
postLoginPhoneVerification: true,
code: result?.code || '',
});
}
return result || {};
}
async function executeBindEmail(state) {
const visibleStep = getVisibleStep(state, 9);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'bind-email';
await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认添加邮箱页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认是否需要绑定邮箱...`,
});
if (pageState?.state === 'oauth_consent_page') {
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱步骤。`, 'warn', {
step: visibleStep,
stepKey: 'bind-email',
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
directOAuthConsentPage: true,
bindEmailSubmitted: false,
});
}
return;
}
if (pageState?.state !== 'add_email_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱步骤只处理添加邮箱页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
const addEmailPreparation = await submitAddEmailIfNeeded(state, visibleStep, pageState);
const preparedState = addEmailPreparation?.state || state;
const nextPageState = addEmailPreparation?.pageState || pageState;
if (nextPageState?.state !== 'verification_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后必须进入邮箱验证码页,当前状态:${nextPageState?.state || 'unknown'}。URL: ${nextPageState?.url || ''}`.trim());
}
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
bindEmailSubmitted: true,
email: preparedState?.email || '',
step8VerificationTargetEmail: preparedState?.step8VerificationTargetEmail || nextPageState?.displayedEmail || '',
});
}
}
async function pollEmailVerificationCode(preparedState, pageState, visibleStep, runtime = {}) {
let latestResendAt = Math.max(
0,
Number(runtime?.stickyLastResendAt) || 0,
Number(preparedState?.loginVerificationRequestedAt) || 0
);
const notifyResendRequestedAt = typeof runtime?.onResendRequestedAt === 'function'
? runtime.onResendRequestedAt
: null;
const mail = getMailConfig(preparedState);
if (mail.error) throw new Error(mail.error);
const stepStartedAt = Date.now();
const verificationFilterAfterTimestamp = mail.provider === '2925'
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: stepStartedAt;
const verificationSessionKey = `${visibleStep}:${stepStartedAt}`;
const shouldCompareVerificationEmail = mail.provider !== '2925';
const displayedVerificationEmail = shouldCompareVerificationEmail
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
: '';
const fixedTargetEmail = shouldCompareVerificationEmail
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(preparedState?.step8VerificationTargetEmail || preparedState?.email))
: '';
await setState({
step8VerificationTargetEmail: displayedVerificationEmail || '',
});
await addLog(`步骤 ${visibleStep}:邮箱验证码页面已就绪,开始获取验证码。`, 'info');
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
}
if (shouldUseCustomRegistrationEmail(preparedState)) {
await confirmCustomVerificationStepBypass(8, {
completionStep: visibleStep,
promptStep: visibleStep,
});
return { lastResendAt: latestResendAt };
}
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
await addLog(`步骤 ${visibleStep}:正在确认 iCloud 邮箱登录态...`, 'info');
await ensureIcloudMailSession({
state: preparedState,
step: 8,
actionLabel: `步骤 ${visibleStep}:确认 iCloud 邮箱登录态`,
});
}
throwIfStopped();
if (
mail.provider === HOTMAIL_PROVIDER
|| mail.provider === LUCKMAIL_PROVIDER
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|| mail.provider === CLOUD_MAIL_PROVIDER
) {
await addLog(`步骤 ${visibleStep}:正在通过 ${mail.label} 轮询验证码...`);
} else {
await addLog(`步骤 ${visibleStep}:正在打开${mail.label}...`);
if (mail.provider === '2925' && typeof ensureMail2925MailboxSession === 'function') {
await ensureMail2925MailboxSession({
accountId: preparedState.currentMail2925AccountId || null,
forceRelogin: false,
allowLoginWhenOnLoginPage: Boolean(preparedState?.mail2925UseAccountPool),
expectedMailboxEmail: getExpectedMail2925MailboxEmail(preparedState),
actionLabel: `Step ${visibleStep}: ensure 2925 mailbox session`,
});
} else {
await focusOrOpenMailTab(mail);
}
if (mail.provider === '2925') {
await addLog(`步骤 ${visibleStep}:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info');
}
}
await resolveVerificationStep(8, {
...preparedState,
step8VerificationTargetEmail: displayedVerificationEmail || '',
}, mail, {
completionStep: visibleStep,
filterAfterTimestamp: verificationFilterAfterTimestamp,
sessionKey: verificationSessionKey,
disableTimeBudgetCap: mail.provider === '2925',
getRemainingTimeMs: getStep8RemainingTimeResolver(preparedState?.oauthUrl || '', visibleStep),
requestFreshCodeFirst: false,
lastResendAt: latestResendAt,
onResendRequestedAt: async (requestedAt) => {
const numericRequestedAt = Number(requestedAt) || 0;
if (numericRequestedAt > 0) {
latestResendAt = Math.max(latestResendAt, numericRequestedAt);
}
if (notifyResendRequestedAt) {
await notifyResendRequestedAt(latestResendAt);
}
},
targetEmail: fixedTargetEmail,
maxResendRequests: mail.provider === '2925' ? 2 : undefined,
initialPollMaxAttempts: mail.provider === '2925' ? 5 : undefined,
pollAttemptPlan: mail.provider === '2925' ? [2, 3, 15] : undefined,
resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER
? 15000
: ((mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
? 0
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS),
});
return {
lastResendAt: latestResendAt,
};
}
async function completeFetchBindEmailCodeSkippedOnOauth(visibleStep, options = {}) {
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱验证码步骤。`, 'warn', {
step: visibleStep,
stepKey: 'fetch-bind-email-code',
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-bind-email-code', {
directOAuthConsentPage: true,
bindEmailCodeSkipped: true,
});
}
}
async function executeFetchBindEmailCode(state) {
const visibleStep = getVisibleStep(state, 10);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'fetch-bind-email-code';
await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱验证码页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认绑定邮箱验证码页...`,
});
if (pageState?.state === 'oauth_consent_page') {
if (state?.bindEmailSubmitted) {
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后不应直接进入 OAuth 授权页,必须先完成邮箱验证码。URL: ${pageState?.url || ''}`.trim());
}
await completeFetchBindEmailCodeSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
return;
}
if (pageState?.state !== 'verification_page') {
throw new Error(`步骤 ${visibleStep}:获取绑定邮箱验证码步骤只处理邮箱验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
if (!state?.bindEmailSubmitted) {
throw new Error(`步骤 ${visibleStep}:尚未完成绑定邮箱提交,不能直接获取绑定邮箱验证码。`);
}
return pollEmailVerificationCode(state, pageState, visibleStep, {
stickyLastResendAt: Number(state?.loginVerificationRequestedAt) || 0,
});
}
async function executeBoundEmailLoginCode(state) {
const visibleStep = getVisibleStep(state, 11);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'fetch-bound-email-login-code';
const preparedState = buildBoundEmailLoginState(state, visibleStep);
const authTabId = await getTabId('signup-page');
if (authTabId) {
await chrome.tabs.update(authTabId, { active: true });
} else {
if (!preparedState.oauthUrl) {
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成绑定邮箱后刷新 OAuth 并登录。`);
}
await reuseOrCreateTab('signup-page', preparedState.oauthUrl);
}
throwIfStopped();
const pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep: Math.max(1, visibleStep - 1),
allowPhoneVerificationPage: true,
allowAddEmailPage: false,
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱登录验证码页已就绪', preparedState?.oauthUrl || '', visibleStep),
});
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, {
nodeId: state?.nodeId || 'fetch-bound-email-login-code',
stepKey: 'fetch-bound-email-login-code',
});
return;
}
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, {
nodeId: state?.nodeId || 'fetch-bound-email-login-code',
stepKey: 'fetch-bound-email-login-code',
});
return;
}
if (pageState?.state === 'add_email_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱后邮箱模式登录不应再进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
}
if (pageState?.state !== 'verification_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱后获取登录验证码只处理邮箱登录验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
return pollEmailVerificationCode(preparedState, pageState, visibleStep, {
stickyLastResendAt: Number(preparedState?.loginVerificationRequestedAt) || 0,
});
}
async function executeBoundEmailPostLoginPhoneVerification(state) {
return executePostLoginPhoneVerification(state, {
stepKey: 'post-bound-email-phone-verification',
fallbackNodeId: 'post-bound-email-phone-verification',
});
}
async function runStep8Attempt(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 8);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'fetch-login-code';
const authTabId = await getTabId('signup-page');
if (authTabId) {
await chrome.tabs.update(authTabId, { active: true });
} else {
if (!state.oauthUrl) {
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}`);
}
await reuseOrCreateTab('signup-page', state.oauthUrl);
}
throwIfStopped();
let pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
allowPhoneVerificationPage: true,
allowAddEmailPage: true,
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
});
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId });
return;
}
const phoneLoginCodeMode = isPhoneLoginCodeMode(state);
if (phoneLoginCodeMode) {
if (pageState?.state === 'phone_verification_page') {
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
}
if (pageState?.state === 'add_email_page') {
await completeStep8WhenDeferredToBindEmail(visibleStep, { nodeId: state?.nodeId });
return;
}
if (pageState?.state === 'verification_page') {
throw new Error(`步骤 ${visibleStep}:手机号注册模式只允许处理手机登录验证码,当前进入了普通邮箱登录验证码页,不会回落到邮箱 provider。URL: ${pageState?.url || ''}`.trim());
}
if (pageState?.state === 'add_phone_page') {
throw new Error(`步骤 ${visibleStep}:手机号注册模式不应进入添加手机号页。URL: ${pageState?.url || ''}`.trim());
}
throw new Error(`步骤 ${visibleStep}:手机号注册模式登录验证码步骤进入了不允许的页面:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, { nodeId: state?.nodeId });
return;
}
if (pageState?.state === 'add_email_page') {
throw new Error(`步骤 ${visibleStep}:邮箱注册模式不应进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
}
return pollEmailVerificationCode(state, pageState, visibleStep, runtime);
}
function isStep8RestartStep7Error(error) {
const message = String(error?.message || error || '');
return /STEP8_RESTART_STEP7::/i.test(message);
}
async function executeStep8(state) {
let currentState = state;
let mailPollingAttempt = 1;
let lastMailPollingError = null;
let stickyLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
let retryWithoutStep7Streak = 0;
const maxRetryWithoutStep7Streak = 3;
while (true) {
try {
const result = await runStep8Attempt(currentState, {
stickyLastResendAt,
onResendRequestedAt: async (requestedAt) => {
const numericRequestedAt = Number(requestedAt) || 0;
if (numericRequestedAt > 0) {
stickyLastResendAt = Math.max(stickyLastResendAt, numericRequestedAt);
}
},
});
if (Number(result?.lastResendAt) > 0) {
stickyLastResendAt = Math.max(stickyLastResendAt, Number(result.lastResendAt) || 0);
}
retryWithoutStep7Streak = 0;
return;
} catch (err) {
const visibleStep = getVisibleStep(currentState, 8);
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
let currentError = err;
let retryWithoutStep7 = false;
const isMailPollingError = isVerificationMailPollingError(err);
if (isMailPollingError && !isStep8RestartStep7Error(err)) {
const recovery = await recoverStep8PollingFailure(currentState, visibleStep);
if (recovery?.outcome === 'completed') {
return;
}
if (recovery?.outcome === 'retry_without_step7') {
retryWithoutStep7 = true;
}
if (recovery?.error) {
currentError = recovery.error;
}
}
if (!isVerificationMailPollingError(currentError) && !isStep8RestartStep7Error(currentError)) {
throw currentError;
}
lastMailPollingError = currentError;
if (mailPollingAttempt >= STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS) {
break;
}
mailPollingAttempt += 1;
if (retryWithoutStep7) {
retryWithoutStep7Streak += 1;
if (retryWithoutStep7Streak > maxRetryWithoutStep7Streak) {
await addLog(
`步骤 ${visibleStep}:邮箱通信异常在当前链路已连续重试 ${retryWithoutStep7Streak} 次,改为回到步骤 ${authLoginStep} 重新发起授权链路,避免空轮询循环。`,
'warn'
);
await rerunStep7ForStep8Recovery({
logMessage: `邮箱通信异常持续未恢复,正在回到步骤 ${authLoginStep} 重新发起登录流程...`,
logStep: visibleStep,
logStepKey: 'fetch-login-code',
});
currentState = await getState();
retryWithoutStep7Streak = 0;
continue;
}
await addLog(
`步骤 ${visibleStep}:认证页仍保持在验证码页,将在当前链路直接重试(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS}),不回到步骤 ${authLoginStep}(连续同链路重试 ${retryWithoutStep7Streak}/${maxRetryWithoutStep7Streak})。`,
'warn'
);
const latestState = await getState();
const latestStateResendAt = Number(latestState?.loginVerificationRequestedAt) || 0;
if (latestStateResendAt > 0) {
stickyLastResendAt = Math.max(stickyLastResendAt, latestStateResendAt);
}
currentState = latestState;
if (stickyLastResendAt > 0 && (!latestStateResendAt || latestStateResendAt < stickyLastResendAt)) {
currentState = {
...latestState,
loginVerificationRequestedAt: stickyLastResendAt,
};
}
const resendIntervalMs = getStep8ResendIntervalMs(currentState);
const remainingBeforeRetryMs = stickyLastResendAt > 0 && resendIntervalMs > 0
? Math.max(0, resendIntervalMs - (Date.now() - stickyLastResendAt))
: 0;
if (remainingBeforeRetryMs > 0 && typeof sleepWithStop === 'function') {
await addLog(
`步骤 ${visibleStep}:上轮已触发重发验证码,为避免重复重发,先等待 ${Math.ceil(remainingBeforeRetryMs / 1000)} 秒后继续当前链路重试。`,
'info'
);
await sleepWithStop(Math.min(remainingBeforeRetryMs, 3000));
}
continue;
}
retryWithoutStep7Streak = 0;
await addLog(
isStep8RestartStep7Error(currentError)
? `步骤 ${visibleStep}:检测到认证页进入重试/超时报错状态,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS}...`
: `步骤 ${visibleStep}:检测到邮箱轮询类失败,准备从步骤 ${authLoginStep} 重新开始(${mailPollingAttempt}/${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS}...`,
'warn'
);
await rerunStep7ForStep8Recovery({
logMessage: isStep8RestartStep7Error(currentError)
? `认证页进入重试/超时报错状态,正在回到步骤 ${authLoginStep} 重新发起登录流程...`
: `正在回到步骤 ${authLoginStep},重新发起登录验证码流程...`,
logStep: visibleStep,
logStepKey: 'fetch-login-code',
});
currentState = await getState();
}
}
const visibleStep = getVisibleStep(currentState, 8);
if (lastMailPollingError) {
throw new Error(
`步骤 ${visibleStep}:登录验证码流程在 ${STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS} 轮邮箱轮询恢复后仍未成功。最后一次原因:${lastMailPollingError.message}`
);
}
throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`);
}
return {
executeStep8,
executePostLoginPhoneVerification,
executeBindEmail,
executeFetchBindEmailCode,
executeBoundEmailLoginCode,
executeBoundEmailPostLoginPhoneVerification,
};
}
return { createStep8Executor };
});
-303
View File
@@ -1,303 +0,0 @@
(function attachBackgroundStep4(root, factory) {
root.MultiPageBackgroundStep4 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep4Module() {
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
function createStep4Executor(deps = {}) {
const {
addLog,
chrome,
completeNodeFromBackground,
confirmCustomVerificationStepBypass,
generateRandomBirthday,
generateRandomName,
ensureMail2925MailboxSession,
ensureIcloudMailSession,
getMailConfig,
getTabId,
HOTMAIL_PROVIDER,
isTabAlive,
LUCKMAIL_PROVIDER,
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
CLOUD_MAIL_PROVIDER = 'cloudmail',
resolveVerificationStep,
reuseOrCreateTab,
sendToContentScript,
sendToContentScriptResilient,
isRetryableContentScriptTransportError = () => false,
shouldUseCustomRegistrationEmail,
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
throwIfStopped,
waitForTabStableComplete = null,
phoneVerificationHelpers = null,
resolveSignupMethod = () => 'email',
} = deps;
function buildSignupProfileForVerificationStep() {
const name = typeof generateRandomName === 'function' ? generateRandomName() : null;
const birthday = typeof generateRandomBirthday === 'function' ? generateRandomBirthday() : null;
if (!name?.firstName || !name?.lastName || !birthday) {
return null;
}
return {
firstName: name.firstName,
lastName: name.lastName,
year: birthday.year,
month: birthday.month,
day: birthday.day,
};
}
function getExpectedMail2925MailboxEmail(state = {}) {
if (Boolean(state?.mail2925UseAccountPool)) {
const currentAccountId = String(state?.currentMail2925AccountId || '').trim();
const accounts = Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
const currentAccount = accounts.find((account) => String(account?.id || '') === currentAccountId) || null;
const accountEmail = String(currentAccount?.email || '').trim().toLowerCase();
if (accountEmail) {
return accountEmail;
}
}
return String(state?.mail2925BaseEmail || '').trim().toLowerCase();
}
function isPhoneSignupState(state = {}) {
return resolveSignupMethod(state) === 'phone'
|| state?.accountIdentifierType === 'phone'
|| Boolean(state?.signupPhoneActivation);
}
async function executeSignupPhoneCodeStep(state, signupTabId) {
if (typeof phoneVerificationHelpers?.completeSignupPhoneVerificationFlow !== 'function') {
throw new Error('步骤 4:手机号注册验证码流程不可用,接码模块尚未初始化。');
}
const signupProfile = buildSignupProfileForVerificationStep();
const result = await phoneVerificationHelpers.completeSignupPhoneVerificationFlow(signupTabId, {
state,
signupProfile,
});
if (result?.emailVerificationRequired || result?.emailVerificationPage) {
return result || {};
}
await completeNodeFromBackground('fetch-signup-code', {
phoneVerification: true,
code: result?.code || '',
...(result?.skipProfileStep ? { skipProfileStep: true } : {}),
...(result?.skipProfileStepReason ? { skipProfileStepReason: result.skipProfileStepReason } : {}),
});
return result || {};
}
async function executeSignupEmailVerificationStep(state, stepStartedAt, verificationSessionKey) {
if (shouldUseCustomRegistrationEmail(state)) {
await confirmCustomVerificationStepBypass(4);
return;
}
const mail = getMailConfig(state);
if (mail.error) throw new Error(mail.error);
const verificationFilterAfterTimestamp = mail.provider === '2925'
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: stepStartedAt;
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
await addLog('步骤 4:正在确认 iCloud 邮箱登录态...', 'info');
await ensureIcloudMailSession({
state,
step: 4,
actionLabel: '步骤 4:确认 iCloud 邮箱登录态',
});
}
throwIfStopped();
if (
mail.provider === HOTMAIL_PROVIDER
|| mail.provider === LUCKMAIL_PROVIDER
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|| mail.provider === CLOUD_MAIL_PROVIDER
) {
await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`);
} else if (mail.provider === '2925') {
await addLog(`步骤 4:正在打开${mail.label}...`);
if (typeof ensureMail2925MailboxSession === 'function') {
await ensureMail2925MailboxSession({
accountId: state.currentMail2925AccountId || null,
forceRelogin: false,
allowLoginWhenOnLoginPage: Boolean(state?.mail2925UseAccountPool),
expectedMailboxEmail: getExpectedMail2925MailboxEmail(state),
actionLabel: '步骤 4:确认 2925 邮箱登录态',
});
} else {
await focusOrOpenMailTab(mail);
}
await addLog(`步骤 4:将直接使用当前已登录的 ${mail.label} 轮询验证码。`, 'info');
} else {
await addLog(`步骤 4:正在打开${mail.label}...`);
await focusOrOpenMailTab(mail);
}
const shouldRequestFreshCodeFirst = ![
HOTMAIL_PROVIDER,
LUCKMAIL_PROVIDER,
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
CLOUD_MAIL_PROVIDER,
].includes(mail.provider);
const signupProfile = buildSignupProfileForVerificationStep();
await resolveVerificationStep(4, state, mail, {
filterAfterTimestamp: verificationFilterAfterTimestamp,
sessionKey: verificationSessionKey,
disableTimeBudgetCap: mail.provider === '2925',
requestFreshCodeFirst: shouldRequestFreshCodeFirst,
signupProfile,
resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER
? 15000
: ((mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
? 0
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS),
});
}
async function focusOrOpenMailTab(mail) {
const alive = await isTabAlive(mail.source);
if (alive) {
if (mail.navigateOnReuse) {
await reuseOrCreateTab(mail.source, mail.url, {
inject: mail.inject,
injectSource: mail.injectSource,
});
return;
}
const tabId = await getTabId(mail.source);
await chrome.tabs.update(tabId, { active: true });
return;
}
await reuseOrCreateTab(mail.source, mail.url, {
inject: mail.inject,
injectSource: mail.injectSource,
});
}
async function executeStep4(state) {
const stepStartedAt = Date.now();
const verificationSessionKey = `4:${stepStartedAt}`;
const signupTabId = await getTabId('signup-page');
if (!signupTabId) {
throw new Error('认证页面标签页已关闭,无法继续步骤 4。请先执行步骤 1 或步骤 2,重新打开认证页后再试。');
}
await chrome.tabs.update(signupTabId, { active: true });
throwIfStopped();
if (typeof waitForTabStableComplete === 'function') {
await addLog('步骤 4:等待注册验证码页面完成加载后再继续...', 'info');
await waitForTabStableComplete(signupTabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 800,
initialDelayMs: 300,
});
}
throwIfStopped();
await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...');
const prepareRequest = {
type: 'PREPARE_SIGNUP_VERIFICATION',
step: 4,
source: 'background',
payload: {
password: state.password || state.customPassword || '',
prepareSource: 'step4_execute',
prepareLogLabel: '步骤 4 执行',
},
};
const prepareTimeoutMs = 30000;
const prepareResponseTimeoutMs = 30000;
const prepareStartAt = Date.now();
let prepareResult = null;
while (Date.now() - prepareStartAt < prepareTimeoutMs) {
throwIfStopped();
try {
prepareResult = typeof sendToContentScript === 'function'
? await sendToContentScript('signup-page', prepareRequest, {
responseTimeoutMs: prepareResponseTimeoutMs,
})
: await sendToContentScriptResilient('signup-page', prepareRequest, {
timeoutMs: Math.max(1000, prepareTimeoutMs - (Date.now() - prepareStartAt)),
responseTimeoutMs: prepareResponseTimeoutMs,
retryDelayMs: 700,
logMessage: '步骤 4:认证页正在切换,等待页面重新就绪后继续检测...',
});
break;
} catch (error) {
if (!isRetryableContentScriptTransportError(error)) {
throw error;
}
const remainingMs = Math.max(0, prepareTimeoutMs - (Date.now() - prepareStartAt));
if (remainingMs <= 0) {
throw error;
}
const recoverResult = await sendToContentScriptResilient('signup-page', {
type: 'RECOVER_AUTH_RETRY_PAGE',
step: 4,
source: 'background',
payload: {
flow: 'signup',
step: 4,
timeoutMs: Math.min(12000, remainingMs),
maxClickAttempts: 2,
logLabel: '步骤 4:检测到注册认证重试页,正在点击“重试”恢复',
},
}, {
timeoutMs: Math.min(12000, remainingMs),
responseTimeoutMs: Math.min(12000, remainingMs),
retryDelayMs: 700,
logMessage: '步骤 4:认证页正在切换,等待页面重新就绪后继续检测...',
});
if (recoverResult?.error) {
throw new Error(recoverResult.error);
}
}
}
if (!prepareResult) {
throw new Error('步骤 4:等待注册验证码页面就绪超时,请刷新认证页后重试。');
}
if (prepareResult && prepareResult.error) {
throw new Error(prepareResult.error);
}
if (prepareResult?.alreadyVerified) {
await completeNodeFromBackground('fetch-signup-code', prepareResult?.skipProfileStep ? { skipProfileStep: true } : {});
return;
}
if (isPhoneSignupState(state)) {
const phoneResult = await executeSignupPhoneCodeStep(state, signupTabId);
if (phoneResult?.emailVerificationRequired || phoneResult?.emailVerificationPage) {
await addLog('步骤 4:手机验证码已通过,OpenAI 要求继续邮箱验证,切换到邮箱验证码轮询。', 'info');
return executeSignupEmailVerificationStep(state, stepStartedAt, verificationSessionKey);
}
return phoneResult;
}
return executeSignupEmailVerificationStep(state, stepStartedAt, verificationSessionKey);
}
return { executeStep4 };
}
return { createStep4Executor };
});
-126
View File
@@ -1,126 +0,0 @@
(function attachBackgroundStep3(root, factory) {
root.MultiPageBackgroundStep3 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep3Module() {
function createStep3Executor(deps = {}) {
const {
addLog,
chrome,
ensureContentScriptReadyOnTab,
generatePassword,
getTabId,
isTabAlive,
resolveSignupMethod,
sendToContentScript,
setPasswordState,
setState,
SIGNUP_PAGE_INJECT_FILES,
} = deps;
function normalizeSignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'phone'
? 'phone'
: 'email';
}
function getResolvedSignupMethodForStep3(state = {}) {
if (typeof resolveSignupMethod === 'function') {
return normalizeSignupMethod(resolveSignupMethod(state));
}
const frozenMethod = String(state?.resolvedSignupMethod || '').trim().toLowerCase();
if (frozenMethod === 'phone' || frozenMethod === 'email') {
return normalizeSignupMethod(frozenMethod);
}
return normalizeSignupMethod(state?.signupMethod);
}
function resolveStep3AccountIdentity(state = {}) {
const resolvedEmail = String(state?.email || '').trim();
const rawAccountIdentifierType = String(state?.accountIdentifierType || '').trim().toLowerCase();
const signupPhoneNumber = String(
state?.signupPhoneNumber
|| (rawAccountIdentifierType === 'phone' ? state?.accountIdentifier : '')
|| ''
).trim();
const explicitEmailIdentity = rawAccountIdentifierType === 'email' && resolvedEmail;
const shouldUsePhoneIdentity = !explicitEmailIdentity && (
rawAccountIdentifierType === 'phone'
|| Boolean(signupPhoneNumber)
|| getResolvedSignupMethodForStep3(state) === 'phone'
);
const accountIdentifierType = shouldUsePhoneIdentity
? 'phone'
: (resolvedEmail ? 'email' : 'email');
const accountIdentifier = accountIdentifierType === 'phone'
? signupPhoneNumber
: resolvedEmail;
return {
accountIdentifierType,
accountIdentifier,
email: resolvedEmail,
phoneNumber: signupPhoneNumber,
};
}
async function executeStep3(state) {
const identity = resolveStep3AccountIdentity(state);
if (!identity.accountIdentifier) {
if (identity.accountIdentifierType === 'phone') {
throw new Error('缺少注册手机号,请先完成步骤 2 或在侧栏填写注册手机号后再执行步骤 3。');
}
throw new Error('缺少注册账号,请先完成步骤 2。');
}
const signupTabId = await getTabId('signup-page');
if (!signupTabId || !(await isTabAlive('signup-page'))) {
throw new Error('认证页面标签页已关闭,请先重新完成步骤 2。');
}
const password = state.customPassword || state.password || generatePassword();
await setPasswordState(password);
const accounts = state.accounts || [];
accounts.push({
email: identity.email,
phoneNumber: identity.phoneNumber,
accountIdentifierType: identity.accountIdentifierType,
accountIdentifier: identity.accountIdentifier,
createdAt: new Date().toISOString(),
});
await setState({ accounts });
await chrome.tabs.update(signupTabId, { active: true });
await ensureContentScriptReadyOnTab('signup-page', signupTabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
timeoutMs: 45000,
retryDelayMs: 900,
logMessage: '步骤 3:密码页内容脚本未就绪,正在等待页面恢复...',
});
const identityLabel = identity.accountIdentifierType === 'phone'
? `注册手机号为 ${identity.accountIdentifier}`
: `邮箱为 ${identity.accountIdentifier}`;
await addLog(
`步骤 3:正在填写密码,${identityLabel},密码为${state.customPassword ? '自定义' : '自动生成'}${password.length} 位)`
);
await sendToContentScript('signup-page', {
type: 'EXECUTE_NODE',
nodeId: 'fill-password',
step: 3,
source: 'background',
payload: {
email: identity.email,
phoneNumber: identity.phoneNumber,
accountIdentifierType: identity.accountIdentifierType,
accountIdentifier: identity.accountIdentifier,
password,
},
});
}
return { executeStep3 };
}
return { createStep3Executor };
});
File diff suppressed because it is too large Load Diff
-37
View File
@@ -1,37 +0,0 @@
(function attachBackgroundStep5(root, factory) {
root.MultiPageBackgroundStep5 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep5Module() {
function createStep5Executor(deps = {}) {
const {
addLog,
generateRandomBirthday,
generateRandomName,
sendToContentScript,
} = deps;
async function executeStep5() {
const { firstName, lastName } = generateRandomName();
const { year, month, day } = generateRandomBirthday();
await addLog(`步骤 5:已生成姓名 ${firstName} ${lastName},生日 ${year}-${month}-${day}`);
await sendToContentScript('signup-page', {
type: 'EXECUTE_NODE',
nodeId: 'fill-profile',
step: 5,
source: 'background',
payload: {
firstName,
lastName,
year,
month,
day,
},
});
}
return { executeStep5 };
}
return { createStep5Executor };
});
File diff suppressed because it is too large Load Diff
-156
View File
@@ -1,156 +0,0 @@
(function attachBackgroundGoPayManualConfirm(root, factory) {
root.MultiPageBackgroundGoPayManualConfirm = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGoPayManualConfirmModule() {
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const GOPAY_CONFIRM_NODE_ID = 'gopay-subscription-confirm';
const SUB2API_SESSION_IMPORT_NODE_ID = 'sub2api-session-import';
const CPA_SESSION_IMPORT_NODE_ID = 'cpa-session-import';
const DEFAULT_CONFIRM_TITLE = 'GoPay 订阅确认';
const OAUTH_CONTINUATION_LABEL = 'OAuth 登录';
const SUB2API_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 SUB2API';
const CPA_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 CPA';
function normalizeString(value = '') {
return String(value || '').trim();
}
function getContinuationActionLabelForNodeId(nodeId = '') {
const normalizedNodeId = normalizeString(nodeId);
if (normalizedNodeId === SUB2API_SESSION_IMPORT_NODE_ID) {
return SUB2API_SESSION_CONTINUATION_LABEL;
}
if (normalizedNodeId === CPA_SESSION_IMPORT_NODE_ID) {
return CPA_SESSION_CONTINUATION_LABEL;
}
return OAUTH_CONTINUATION_LABEL;
}
function getContinuationActionLabel(state = {}, options = {}) {
const { getNodeIdsForState = null } = options;
if (typeof getNodeIdsForState === 'function') {
const nodeIds = getNodeIdsForState(state)
.map((nodeId) => normalizeString(nodeId))
.filter(Boolean);
const currentNodeId = normalizeString(state?.nodeId || GOPAY_CONFIRM_NODE_ID) || GOPAY_CONFIRM_NODE_ID;
const currentNodeIndex = nodeIds.indexOf(currentNodeId);
const nextNodeId = currentNodeIndex >= 0
? normalizeString(nodeIds[currentNodeIndex + 1])
: '';
if (nextNodeId) {
return getContinuationActionLabelForNodeId(nextNodeId);
}
if (currentNodeIndex < 0) {
if (nodeIds.includes(SUB2API_SESSION_IMPORT_NODE_ID)) {
return SUB2API_SESSION_CONTINUATION_LABEL;
}
if (nodeIds.includes(CPA_SESSION_IMPORT_NODE_ID)) {
return CPA_SESSION_CONTINUATION_LABEL;
}
}
}
return OAUTH_CONTINUATION_LABEL;
}
function buildDefaultConfirmMessage(state = {}, options = {}) {
const continuationActionLabel = getContinuationActionLabel(state, options);
return `GoPay 订阅页已打开。请先手动完成订阅,完成后确认继续${continuationActionLabel}`;
}
function createGoPayManualConfirmExecutor(deps = {}) {
const {
addLog,
broadcastDataUpdate,
chrome,
createAutomationTab = null,
getTabId,
getNodeIdsForState = null,
isTabAlive,
registerTab,
setState,
} = deps;
function buildRequestId() {
return `gopay-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
async function resolveCheckoutTabId(state = {}) {
const registeredTabId = typeof getTabId === 'function'
? await getTabId(PLUS_CHECKOUT_SOURCE)
: null;
if (registeredTabId && typeof isTabAlive === 'function' && await isTabAlive(PLUS_CHECKOUT_SOURCE)) {
return Number(registeredTabId) || 0;
}
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
if (storedTabId && chrome?.tabs?.get) {
const tab = await chrome.tabs.get(storedTabId).catch(() => null);
if (tab?.id) {
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, tab.id);
}
return tab.id;
}
}
const checkoutUrl = normalizeString(state?.plusCheckoutUrl);
if (!checkoutUrl) {
throw new Error('步骤 7:未检测到 GoPay 订阅页,请先执行步骤 6。');
}
if (!chrome?.tabs?.create) {
throw new Error('步骤 7:无法自动重新打开 GoPay 订阅页。');
}
const tab = typeof createAutomationTab === 'function'
? await createAutomationTab({ url: checkoutUrl, active: true })
: await chrome.tabs.create({ url: checkoutUrl, active: true });
const tabId = Number(tab?.id) || 0;
if (!tabId) {
throw new Error('步骤 7:重新打开 GoPay 订阅页失败。');
}
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, tabId);
}
return tabId;
}
async function executeGoPayManualConfirm(state = {}) {
const visibleStep = Number(state?.visibleStep) || 7;
const tabId = await resolveCheckoutTabId(state);
if (chrome?.tabs?.update && tabId) {
await chrome.tabs.update(tabId, { active: true }).catch(() => {});
}
const continuationActionLabel = getContinuationActionLabel(state, { getNodeIdsForState });
const payload = {
plusCheckoutTabId: tabId,
plusManualConfirmationPending: true,
plusManualConfirmationRequestId: buildRequestId(),
plusManualConfirmationStep: visibleStep,
plusManualConfirmationMethod: 'gopay',
plusManualConfirmationTitle: DEFAULT_CONFIRM_TITLE,
plusManualConfirmationMessage: buildDefaultConfirmMessage(state, { getNodeIdsForState }),
};
await setState(payload);
if (typeof broadcastDataUpdate === 'function') {
broadcastDataUpdate(payload);
}
await addLog(
`步骤 ${visibleStep}:正在等待手动完成 GoPay 订阅,确认后继续${continuationActionLabel}`,
'info'
);
}
return {
executeGoPayManualConfirm,
};
}
return {
createGoPayManualConfirmExecutor,
};
});
-403
View File
@@ -1,403 +0,0 @@
(function attachBackgroundStep7(root, factory) {
root.MultiPageBackgroundStep7 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep7Module() {
function createStep7Executor(deps = {}) {
const {
addLog,
completeNodeFromBackground,
getErrorMessage,
getLoginAuthStateLabel,
getOAuthFlowStepTimeoutMs,
getState,
isAddPhoneAuthFailure = (error) => {
const message = String(typeof error === 'string' ? error : error?.message || '');
if (/\u624b\u673a\u53f7\u8f93\u5165\u6a21\u5f0f|phone\s+entry/i.test(message)) {
return false;
}
return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)|\badd-phone\b|\u6dfb\u52a0\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u8fdb\u5165\u624b\u673a\u53f7\u9875\u9762|\u624b\u673a\u53f7\u9875|\u624b\u673a\u53f7\u9875\u9762|phone\s+number|telephone/i.test(message);
},
isStep6RecoverableResult,
isStep6SuccessResult,
getTabId,
refreshOAuthUrlBeforeStep6,
reuseOrCreateTab,
sendToContentScriptResilient,
startOAuthFlowTimeoutWindow,
STEP6_MAX_ATTEMPTS,
throwIfStopped,
} = deps;
function isManagementSecretConfigError(error) {
const message = String(typeof error === 'string' ? error : error?.message || '').trim();
if (!message) {
return false;
}
const mentionsSecret = /管理密钥|Admin Secret|X-Admin-Key|CPA Key/i.test(message);
if (!mentionsSecret) {
return false;
}
return /缺少|未配置|请输入|无效|错误|失败|401|认证失败|未授权|unauthorized|invalid/i.test(message);
}
function normalizeStep7IdentifierType(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'phone' || normalized === 'email' ? normalized : '';
}
function normalizeStep7SignupMethod(value = '') {
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function shouldForceStep7EmailLogin(state = {}) {
return normalizeStep7IdentifierType(state?.forceLoginIdentifierType) === 'email'
|| Boolean(state?.forceEmailLogin);
}
function canUseConfiguredPhoneSignup(state = {}) {
return normalizeStep7SignupMethod(state?.signupMethod) === 'phone'
&& Boolean(state?.phoneVerificationEnabled)
&& !Boolean(state?.plusModeEnabled)
&& !Boolean(state?.accountContributionEnabled);
}
function hasStep7PhoneSignupIdentity(state = {}) {
return Boolean(
String(state?.signupPhoneNumber || '').trim()
|| String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim()
|| String(state?.signupPhoneActivation?.phoneNumber || '').trim()
|| (
normalizeStep7IdentifierType(state?.accountIdentifierType) === 'phone'
&& String(state?.accountIdentifier || '').trim()
)
);
}
function shouldPreferStep7PhoneSignupIdentity(state = {}) {
return canUseConfiguredPhoneSignup(state)
&& hasStep7PhoneSignupIdentity(state);
}
function resolveStep7LoginIdentifierType(state = {}, fallbackType = '') {
if (shouldForceStep7EmailLogin(state)) {
return 'email';
}
if (shouldPreferStep7PhoneSignupIdentity(state)) {
return 'phone';
}
const explicitIdentifierType = normalizeStep7IdentifierType(state?.accountIdentifierType);
if (explicitIdentifierType) {
return explicitIdentifierType;
}
const frozenSignupMethod = normalizeStep7IdentifierType(state?.resolvedSignupMethod);
if (frozenSignupMethod) {
return frozenSignupMethod;
}
if (canUseConfiguredPhoneSignup(state)) {
return 'phone';
}
return normalizeStep7IdentifierType(fallbackType) || 'email';
}
function extractAddPhoneUrl(error) {
const message = String(typeof error === 'string' ? error : error?.message || '');
const match = message.match(/https:\/\/auth\.openai\.com\/add-phone(?:[^\s]*)?/i);
return match ? match[0] : 'https://auth.openai.com/add-phone';
}
function getStep7ResultState(result = {}) {
return String(result?.state || '').trim();
}
function isStep7OauthConsentResult(result = {}) {
return Boolean(result?.directOAuthConsentPage)
|| getStep7ResultState(result) === 'oauth_consent_page';
}
function isStep7AddEmailResult(result = {}) {
return Boolean(result?.addEmailPage) || getStep7ResultState(result) === 'add_email_page';
}
function isStep7AddPhoneResult(result = {}) {
return Boolean(result?.addPhonePage) || getStep7ResultState(result) === 'add_phone_page';
}
function isStep7PhoneVerificationResult(result = {}) {
return Boolean(result?.phoneVerificationPage) || getStep7ResultState(result) === 'phone_verification_page';
}
function isStep7PlainVerificationResult(result = {}) {
return getStep7ResultState(result) === 'verification_page' && !isStep7PhoneVerificationResult(result);
}
function buildStep7CompletionPayload(result = {}, currentState = {}, currentIdentifierType = '', currentPhoneNumber = '') {
const phoneSignupMode = currentIdentifierType === 'phone';
const payload = {
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
};
if (currentIdentifierType === 'phone') {
payload.accountIdentifierType = 'phone';
payload.accountIdentifier = currentPhoneNumber;
payload.signupPhoneNumber = currentPhoneNumber;
payload.signupPhoneCompletedActivation = currentState?.signupPhoneCompletedActivation || null;
payload.signupPhoneActivation = currentState?.signupPhoneActivation || null;
}
if (isStep7OauthConsentResult(result)) {
payload.skipLoginVerificationStep = true;
payload.directOAuthConsentPage = true;
return payload;
}
if (phoneSignupMode) {
if (isStep7AddPhoneResult(result)) {
throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录不应进入添加手机号页。URL: ${result?.url || ''}`.trim());
}
if (isStep7AddEmailResult(result)) {
payload.skipLoginVerificationStep = true;
payload.addEmailPage = true;
return payload;
}
if (isStep7PhoneVerificationResult(result)) {
return payload;
}
if (isStep7PlainVerificationResult(result)) {
throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录进入了普通邮箱登录验证码页,当前流程不会回落到邮箱验证码。URL: ${result?.url || ''}`.trim());
}
throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录进入了不允许的页面:${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim());
}
if (isStep7AddEmailResult(result)) {
throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录不应进入添加邮箱页。URL: ${result?.url || ''}`.trim());
}
if (isStep7AddPhoneResult(result) || isStep7PhoneVerificationResult(result)) {
payload.skipLoginVerificationStep = true;
payload.addPhonePage = isStep7AddPhoneResult(result);
payload.phoneVerificationPage = isStep7PhoneVerificationResult(result);
return payload;
}
if (isStep7PlainVerificationResult(result)) {
return payload;
}
throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录进入了不允许的页面:${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim());
}
function completionStepForState(state = {}) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : 7;
}
async function completeStep7PostLoginPhoneHandoff(state = {}, err, completionStep) {
if (normalizeStep7SignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone') {
throw new Error(
`步骤 ${completionStep}:手机号注册模式 OAuth 登录进入了添加手机号页,当前流程不允许在手机号注册模式补手机号。URL: ${extractAddPhoneUrl(err)}`
);
}
await completeNodeFromBackground(state?.nodeId || 'oauth-login', {
loginVerificationRequestedAt: null,
skipLoginVerificationStep: true,
addPhonePage: true,
directOAuthConsentPage: false,
});
}
async function executeStep7(state) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
const completionStep = visibleStep > 0 ? visibleStep : 7;
const resolvedIdentifierType = resolveStep7LoginIdentifierType(state);
const phoneNumber = resolvedIdentifierType === 'phone'
? String(
state?.signupPhoneNumber
|| (normalizeStep7IdentifierType(state?.accountIdentifierType) === 'phone' ? state?.accountIdentifier : '')
|| state?.signupPhoneCompletedActivation?.phoneNumber
|| state?.signupPhoneActivation?.phoneNumber
|| ''
).trim()
: '';
const email = resolvedIdentifierType === 'email'
? String(
state?.email
|| (normalizeStep7IdentifierType(state?.accountIdentifierType) === 'email' ? state?.accountIdentifier : '')
|| ''
).trim()
: '';
if (
(resolvedIdentifierType === 'phone' && !phoneNumber)
|| (resolvedIdentifierType !== 'phone' && !email)
) {
throw new Error('缺少登录账号:请先完成步骤 2,或在侧栏“注册邮箱/注册手机号”中手动填写账号后再执行当前步骤。');
}
let attempt = 0;
let lastError = null;
while (attempt < STEP6_MAX_ATTEMPTS) {
throwIfStopped();
attempt += 1;
try {
const rawCurrentState = attempt === 1 ? state : await getState();
const currentState = shouldForceStep7EmailLogin(state)
? {
...rawCurrentState,
forceLoginIdentifierType: 'email',
forceEmailLogin: true,
signupMethod: 'email',
resolvedSignupMethod: 'email',
accountIdentifierType: 'email',
accountIdentifier: email,
email,
}
: rawCurrentState;
const password = currentState.password || currentState.customPassword || '';
const currentIdentifierType = resolveStep7LoginIdentifierType(currentState, resolvedIdentifierType);
const currentPhoneNumber = currentIdentifierType === 'phone'
? String(
currentState?.signupPhoneNumber
|| (normalizeStep7IdentifierType(currentState?.accountIdentifierType) === 'phone' ? currentState?.accountIdentifier : '')
|| currentState?.signupPhoneCompletedActivation?.phoneNumber
|| currentState?.signupPhoneActivation?.phoneNumber
|| phoneNumber
).trim()
: '';
const currentEmail = currentIdentifierType === 'email'
? String(
currentState?.email
|| (normalizeStep7IdentifierType(currentState?.accountIdentifierType) === 'email' ? currentState?.accountIdentifier : '')
|| email
).trim()
: '';
const accountIdentifier = currentIdentifierType === 'phone'
? currentPhoneNumber
: currentEmail;
const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState);
if (typeof startOAuthFlowTimeoutWindow === 'function') {
await startOAuthFlowTimeoutWindow({ step: completionStep, oauthUrl });
}
const loginTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(180000, {
step: completionStep,
actionLabel: 'OAuth 登录并进入验证码页',
oauthUrl,
})
: 180000;
if (attempt === 1) {
await addLog('正在打开最新 OAuth 链接并登录...', 'info', {
step: completionStep,
stepKey: 'oauth-login',
});
} else {
await addLog(`上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn', {
step: completionStep,
stepKey: 'oauth-login',
});
}
await reuseOrCreateTab('signup-page', oauthUrl, { forceNew: true });
const result = await sendToContentScriptResilient(
'signup-page',
{
type: 'EXECUTE_NODE',
nodeId: state?.nodeId || 'oauth-login',
step: 7,
source: 'background',
payload: {
email: currentEmail,
phoneNumber: currentPhoneNumber,
countryId: currentState?.signupPhoneCompletedActivation?.countryId
?? currentState?.signupPhoneActivation?.countryId
?? null,
countryLabel: String(
currentState?.signupPhoneCompletedActivation?.countryLabel
|| currentState?.signupPhoneActivation?.countryLabel
|| ''
).trim(),
accountIdentifier,
loginIdentifierType: currentIdentifierType,
password,
visibleStep: completionStep,
},
},
{
timeoutMs: loginTimeoutMs,
responseTimeoutMs: loginTimeoutMs,
retryDelayMs: 700,
logMessage: '认证页正在切换,等待页面重新就绪后继续登录...',
logStep: completionStep,
logStepKey: 'oauth-login',
}
);
if (result?.error) {
throw new Error(result.error);
}
if (isStep6SuccessResult(result)) {
const completionPayload = buildStep7CompletionPayload(
result,
{ ...(currentState || {}), visibleStep: completionStep },
currentIdentifierType,
currentPhoneNumber
);
await completeNodeFromBackground(state?.nodeId || 'oauth-login', completionPayload);
return;
}
if (isStep6RecoverableResult(result)) {
const reasonMessage = result.message
|| `当前停留在${getLoginAuthStateLabel(result.state)},准备重新执行步骤 ${completionStep}`;
throw new Error(reasonMessage);
}
throw new Error(`步骤 ${completionStep}:认证页未返回可识别的登录结果。`);
} catch (err) {
throwIfStopped(err);
if (isAddPhoneAuthFailure(err)) {
const latestAddPhoneState = typeof getState === 'function'
? await getState().catch(() => state)
: state;
await completeStep7PostLoginPhoneHandoff(
{ ...(state || {}), ...(latestAddPhoneState || {}) },
err,
completionStep
);
return;
}
if (isManagementSecretConfigError(err)) {
await addLog(
`检测到来源后台管理密钥缺失或错误,不再重试,当前流程停止。原因:${getErrorMessage(err)}`,
'error',
{ step: completionStep, stepKey: 'oauth-login' }
);
throw err;
}
lastError = err;
if (attempt >= STEP6_MAX_ATTEMPTS) {
break;
}
await addLog(`${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn', {
step: completionStep,
stepKey: 'oauth-login',
});
}
}
throw new Error(`步骤 ${completionStep}:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`);
}
return { executeStep7 };
}
return { createStep7Executor };
});
-159
View File
@@ -1,159 +0,0 @@
(function attachBackgroundStep1(root, factory) {
root.MultiPageBackgroundStep1 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep1Module() {
const STEP1_COOKIE_CLEAR_DOMAINS = [
'chatgpt.com',
'chat.openai.com',
'openai.com',
'auth.openai.com',
'auth0.openai.com',
'accounts.openai.com',
];
function normalizeCookieDomainForStep1(domain) {
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
}
function shouldClearStep1Cookie(cookie) {
const domain = normalizeCookieDomainForStep1(cookie?.domain);
if (!domain) return false;
return STEP1_COOKIE_CLEAR_DOMAINS.some((target) => (
domain === target || domain.endsWith(`.${target}`)
));
}
function buildStep1CookieKey(cookie, fallbackStoreId = '') {
return [
cookie?.storeId || fallbackStoreId || '',
cookie?.domain || '',
cookie?.path || '',
cookie?.name || '',
cookie?.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
].join('|');
}
function buildStep1CookieRemovalUrl(cookie) {
const host = normalizeCookieDomainForStep1(cookie?.domain);
const rawPath = String(cookie?.path || '/');
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
return `https://${host}${path}`;
}
function getStep1ErrorMessage(error) {
return error?.message || String(error || '未知错误');
}
async function collectStep1Cookies(chromeApi) {
if (!chromeApi.cookies?.getAll) {
return [];
}
const stores = chromeApi.cookies.getAllCookieStores
? await chromeApi.cookies.getAllCookieStores()
: [{ id: undefined }];
const cookies = [];
const seen = new Set();
const queryDomains = Array.from(
new Set(
STEP1_COOKIE_CLEAR_DOMAINS
.map((domain) => normalizeCookieDomainForStep1(domain))
.filter(Boolean)
)
);
for (const store of stores) {
const storeId = store?.id;
for (const domain of queryDomains) {
let batch = [];
try {
batch = await chromeApi.cookies.getAll(
storeId
? { storeId, domain }
: { domain }
);
} catch (error) {
console.warn('[MultiPage:step1] query cookies failed', {
storeId: storeId || '',
domain,
message: getStep1ErrorMessage(error),
});
continue;
}
for (const cookie of batch || []) {
if (!shouldClearStep1Cookie(cookie)) continue;
const key = buildStep1CookieKey(cookie, storeId);
if (seen.has(key)) continue;
seen.add(key);
cookies.push(cookie);
}
}
}
return cookies;
}
async function removeStep1Cookie(chromeApi, cookie) {
const details = {
url: buildStep1CookieRemovalUrl(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:step1] remove cookie failed', {
domain: cookie?.domain,
name: cookie?.name,
message: getStep1ErrorMessage(error),
});
return false;
}
}
function createStep1Executor(deps = {}) {
const {
addLog,
chrome: chromeApi = globalThis.chrome,
completeNodeFromBackground,
openSignupEntryTab,
} = deps;
async function clearOpenAiCookiesBeforeStep1() {
if (!chromeApi?.cookies?.getAll || !chromeApi.cookies?.remove) {
await addLog('步骤 1:当前浏览器不支持 cookies API,跳过打开官网前 cookie 清理。', 'warn');
return;
}
const startedAt = Date.now();
await addLog('步骤 1:打开 ChatGPT 官网前清理 ChatGPT / OpenAI cookies...', 'info');
const cookies = await collectStep1Cookies(chromeApi);
let removedCount = 0;
for (const cookie of cookies) {
if (await removeStep1Cookie(chromeApi, cookie)) {
removedCount += 1;
}
}
const elapsedMs = Date.now() - startedAt;
await addLog(`步骤 1:已清理 ${removedCount} 个 ChatGPT / OpenAI cookies(耗时 ${elapsedMs}ms)。`, 'ok');
}
async function executeStep1() {
await clearOpenAiCookiesBeforeStep1();
await addLog('步骤 1:正在打开 ChatGPT 官网...');
await openSignupEntryTab(1);
await completeNodeFromBackground('open-chatgpt', {});
}
return { executeStep1 };
}
return { createStep1Executor };
});
-310
View File
@@ -1,310 +0,0 @@
(function attachBackgroundPayPalApprove(root, factory) {
root.MultiPageBackgroundPayPalApprove = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPayPalApproveModule() {
const PAYPAL_SOURCE = 'paypal-flow';
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/paypal-flow.js'];
const PAYPAL_LOGIN_TRANSITION_TIMEOUT_MS = 30000;
const PAYPAL_LOGIN_TRANSITION_POLL_MS = 500;
function createPayPalApproveExecutor(deps = {}) {
const {
addLog,
chrome,
completeNodeFromBackground,
ensureContentScriptReadyOnTabUntilStopped,
getTabId,
isTabAlive,
queryTabsInAutomationWindow = null,
sendTabMessageUntilStopped,
setState,
sleepWithStop,
waitForTabCompleteUntilStopped,
waitForTabUrlMatchUntilStopped,
} = deps;
async function resolvePayPalTabId(state = {}) {
const paypalTabId = await getTabId(PAYPAL_SOURCE);
if (paypalTabId && await isTabAlive(PAYPAL_SOURCE)) {
return paypalTabId;
}
const discoveredPayPalTabId = await findOpenPayPalTabId();
if (discoveredPayPalTabId) {
await addLog('步骤 8:已从当前浏览器标签中发现 PayPal 页面,正在接管继续执行。', 'info');
return discoveredPayPalTabId;
}
const checkoutTabId = await getTabId(PLUS_CHECKOUT_SOURCE);
if (checkoutTabId) {
return checkoutTabId;
}
const storedTabId = Number(state.plusCheckoutTabId) || 0;
if (storedTabId) {
return storedTabId;
}
throw new Error('步骤 8:未找到 PayPal 标签页,请先完成步骤 7。');
}
async function findOpenPayPalTabId() {
if (!chrome?.tabs?.query) {
return 0;
}
const queryTabs = typeof queryTabsInAutomationWindow === 'function'
? queryTabsInAutomationWindow
: (queryInfo) => chrome.tabs.query(queryInfo);
const tabs = await queryTabs({}).catch(() => []);
const candidates = (Array.isArray(tabs) ? tabs : [])
.filter((tab) => Number.isInteger(tab?.id) && isPayPalUrl(tab.url || ''));
if (!candidates.length) {
return 0;
}
const match = candidates.find((tab) => tab.active && tab.currentWindow)
|| candidates.find((tab) => tab.active)
|| candidates[0];
if (match?.id && chrome?.tabs?.update) {
await chrome.tabs.update(match.id, { active: true }).catch(() => {});
}
return match?.id || 0;
}
async function ensurePayPalReady(tabId, logMessage = '') {
await waitForTabUrlMatchUntilStopped(tabId, (url) => /paypal\./i.test(url));
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, {
inject: PAYPAL_INJECT_FILES,
injectSource: PAYPAL_SOURCE,
logMessage: logMessage || '步骤 8:PayPal 页面仍在加载,等待脚本就绪...',
});
}
async function getPayPalState(tabId) {
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_GET_STATE',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function dismissPrompts(tabId) {
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_DISMISS_PROMPTS',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
function resolvePayPalCredentials(state = {}) {
const currentId = String(state?.currentPayPalAccountId || '').trim();
const accounts = Array.isArray(state?.paypalAccounts) ? state.paypalAccounts : [];
const selectedAccount = currentId
? accounts.find((account) => String(account?.id || '').trim() === currentId) || null
: null;
return {
email: String(selectedAccount?.email || state?.paypalEmail || '').trim(),
password: String(selectedAccount?.password || state?.paypalPassword || ''),
};
}
async function submitLogin(tabId, state = {}) {
const credentials = resolvePayPalCredentials(state);
if (!credentials.password) {
throw new Error('步骤 8:未配置可用的 PayPal 账号,请先在侧边栏添加并选择账号。');
}
await addLog('步骤 8:正在填写 PayPal 登录信息并提交...', 'info');
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_SUBMIT_LOGIN',
source: 'background',
payload: {
email: credentials.email,
password: credentials.password,
},
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
function isPayPalUrl(url = '') {
return /paypal\./i.test(String(url || ''));
}
function isPayPalPasswordState(pageState = {}) {
return Boolean(pageState.hasPasswordInput)
|| pageState.loginPhase === 'password'
|| pageState.loginPhase === 'login_combined';
}
async function waitForPayPalPostLoginDecision(tabId, actionResult = {}) {
const phase = String(actionResult?.phase || '').trim();
const startedAt = Date.now();
while (Date.now() - startedAt < PAYPAL_LOGIN_TRANSITION_TIMEOUT_MS) {
const tab = await chrome.tabs.get(tabId).catch(() => null);
if (!tab) {
throw new Error('步骤 8:PayPal 标签页已关闭,无法继续识别登录后的页面。');
}
const currentUrl = tab.url || '';
if (!currentUrl) {
await sleepWithStop(PAYPAL_LOGIN_TRANSITION_POLL_MS);
continue;
}
if (currentUrl && !isPayPalUrl(currentUrl)) {
return {
outcome: 'left_paypal',
url: currentUrl,
};
}
if (tab.status !== 'complete') {
await sleepWithStop(PAYPAL_LOGIN_TRANSITION_POLL_MS);
continue;
}
await ensurePayPalReady(
tabId,
phase === 'email_submitted'
? '步骤 8:PayPal 账号已提交,正在识别下一页...'
: '步骤 8:PayPal 密码已提交,正在识别跳转结果...'
);
const pageState = await getPayPalState(tabId);
if (pageState.hasPasskeyPrompt) {
return {
outcome: 'prompt',
pageState,
};
}
if (pageState.approveReady) {
return {
outcome: 'approve_ready',
pageState,
};
}
if (phase === 'email_submitted' && isPayPalPasswordState(pageState)) {
return {
outcome: 'password_ready',
pageState,
};
}
if (phase === 'password_submitted' && !pageState.needsLogin) {
return {
outcome: 'post_login_state',
pageState,
};
}
await sleepWithStop(PAYPAL_LOGIN_TRANSITION_POLL_MS);
}
return {
outcome: 'timeout',
phase,
};
}
async function clickApprove(tabId) {
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_CLICK_APPROVE',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return Boolean(result?.clicked);
}
async function executePayPalApprove(state = {}) {
const tabId = await resolvePayPalTabId(state);
await ensurePayPalReady(tabId);
await setState({ plusCheckoutTabId: tabId });
let loggedWaiting = false;
while (true) {
const currentUrl = (await chrome.tabs.get(tabId).catch(() => null))?.url || '';
if (currentUrl && !isPayPalUrl(currentUrl)) {
await addLog('步骤 8:PayPal 已跳转离开授权页,准备进入回跳确认。', 'ok');
break;
}
await ensurePayPalReady(tabId, '步骤 8:PayPal 页面正在切换,等待脚本重新就绪...');
const pageState = await getPayPalState(tabId);
if (pageState.needsLogin) {
const submitResult = await submitLogin(tabId, state);
const decision = await waitForPayPalPostLoginDecision(tabId, submitResult);
if (decision.outcome === 'left_paypal') {
await addLog('步骤 8:PayPal 登录后已跳转离开登录/授权页,继续进入回跳确认。', 'ok');
break;
}
if (decision.outcome === 'password_ready') {
await addLog('步骤 8:PayPal 账号页提交后已识别到密码页,继续填写密码。', 'info');
} else if (decision.outcome === 'approve_ready') {
await addLog('步骤 8:PayPal 登录后已识别到授权确认页,继续点击授权。', 'info');
} else if (decision.outcome === 'prompt') {
await addLog('步骤 8:PayPal 登录后已识别到提示弹窗,继续处理弹窗。', 'info');
} else if (decision.outcome === 'timeout') {
await addLog('步骤 8:PayPal 登录动作后暂未识别到新页面,重新检查当前页面状态。', 'warn');
}
loggedWaiting = false;
continue;
}
if (pageState.hasPasskeyPrompt) {
await addLog('步骤 8:检测到 PayPal 通行密钥提示,正在关闭...', 'info');
await dismissPrompts(tabId);
await sleepWithStop(1000);
continue;
}
const dismissed = await dismissPrompts(tabId).catch(() => ({ clicked: 0 }));
if (dismissed.clicked) {
await sleepWithStop(1000);
continue;
}
if (pageState.approveReady) {
await addLog('步骤 8:正在点击 PayPal“同意并继续”...', 'info');
const clicked = await clickApprove(tabId);
if (clicked) {
await setState({ plusPaypalApprovedAt: Date.now() });
break;
}
}
if (!loggedWaiting) {
loggedWaiting = true;
await addLog('步骤 8:等待 PayPal 授权按钮或下一步页面出现...', 'info');
}
await sleepWithStop(500);
}
await completeNodeFromBackground('paypal-approve', {
plusPaypalApprovedAt: Date.now(),
});
}
return {
executePayPalApprove,
};
}
return {
createPayPalApproveExecutor,
};
});
-434
View File
@@ -1,434 +0,0 @@
(function attachBackgroundStep10(root, factory) {
root.MultiPageBackgroundStep10 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep10Module() {
function createStep10Executor(deps = {}) {
const {
addLog,
chrome,
closeConflictingTabsForSource,
completeNodeFromBackground,
ensureContentScriptReadyOnTab,
getPanelMode,
getTabId,
getStepIdByKeyForState,
isLocalhostOAuthCallbackUrl,
isTabAlive,
normalizeCodex2ApiUrl,
normalizeSub2ApiUrl,
rememberSourceLastUrl,
reuseOrCreateTab,
sendToContentScript,
sendToContentScriptResilient,
shouldBypassStep9ForLocalCpa,
DEFAULT_SUB2API_GROUP_NAME = 'codex',
SUB2API_STEP9_RESPONSE_TIMEOUT_MS,
} = deps;
let sub2ApiApi = null;
function getSub2ApiApi() {
if (sub2ApiApi) {
return sub2ApiApi;
}
const factory = deps.createSub2ApiApi
|| self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi;
if (typeof factory !== 'function') {
throw new Error('SUB2API 直连接口模块未加载,无法提交回调。');
}
sub2ApiApi = factory({
addLog,
normalizeSub2ApiUrl,
DEFAULT_SUB2API_GROUP_NAME,
});
return sub2ApiApi;
}
function normalizeString(value = '') {
return String(value || '').trim();
}
function resolvePlatformVerifyStep(state = {}) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep >= 10 ? visibleStep : 10;
}
function resolveStepIdByKey(state = {}, stepKey = '') {
if (typeof getStepIdByKeyForState !== 'function') {
return null;
}
const step = Number(getStepIdByKeyForState(stepKey, state));
return Number.isInteger(step) && step > 0 ? step : null;
}
function resolveConfirmOauthStep(platformVerifyStep = 10, state = {}) {
const dynamicStep = resolveStepIdByKey(state, 'confirm-oauth');
if (dynamicStep && dynamicStep < Number(platformVerifyStep)) {
return dynamicStep;
}
return Math.max(1, Number(platformVerifyStep) - 1);
}
function resolveAuthLoginStep(platformVerifyStep = 10, state = {}) {
const reloginStep = resolveStepIdByKey(state, 'relogin-bound-email');
if (reloginStep && reloginStep < Number(platformVerifyStep)) {
return reloginStep;
}
const oauthLoginStep = resolveStepIdByKey(state, 'oauth-login');
if (oauthLoginStep && oauthLoginStep < Number(platformVerifyStep)) {
return oauthLoginStep;
}
return Number(platformVerifyStep) >= 13 ? 10 : 7;
}
function addStepLog(step, message, level = 'info') {
return addLog(message, level, { step, stepKey: 'platform-verify' });
}
function parseLocalhostCallback(rawUrl, platformVerifyStep = 10, state = {}) {
const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep, state);
let parsed;
try {
parsed = new URL(rawUrl);
} catch {
throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址格式无效,请重新执行步骤 ${confirmOauthStep}`);
}
const code = normalizeString(parsed.searchParams.get('code'));
const oauthState = normalizeString(parsed.searchParams.get('state'));
if (!code || !oauthState) {
throw new Error(`步骤 ${platformVerifyStep} 捕获到的 localhost OAuth 回调地址缺少 code 或 state,请重新执行步骤 ${confirmOauthStep}`);
}
return {
url: parsed.toString(),
code,
state: oauthState,
};
}
function getCodex2ApiErrorMessage(payload, responseStatus = 500) {
const details = [
payload?.error,
payload?.message,
payload?.detail,
payload?.reason,
]
.map((value) => normalizeString(value))
.find(Boolean);
return details || `Codex2API 请求失败(HTTP ${responseStatus})。`;
}
function deriveCpaManagementOrigin(vpsUrl) {
const normalizedUrl = normalizeString(vpsUrl);
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 details = [
payload?.error,
payload?.message,
payload?.detail,
payload?.reason,
]
.map((value) => normalizeString(value))
.find(Boolean);
return details || `CPA 管理接口请求失败(HTTP ${responseStatus})。`;
}
async function fetchCpaManagementJson(origin, path, options = {}) {
const controller = new AbortController();
const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 20000));
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const managementKey = normalizeString(options.managementKey);
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);
}
}
function isSub2ApiTransientExchangeError(error) {
const message = normalizeString(error?.message || error);
if (!message) {
return false;
}
const tokenExchangeFailure = /auth\.openai\.com\/oauth\/token/i.test(message);
const transientNetworkSignal = /unexpected\s+eof|eof|connection\s+refused|i\/o\s+timeout|context\s+deadline\s+exceeded|connection\s+reset|broken\s+pipe|failed\s+to\s+fetch|temporarily\s+unavailable|timeout/i.test(message);
const transientExchangeUserSignal = /token_exchange_user_error|invalid\s+request\.\s+please\s+try\s+again\s+later/i.test(message);
if (transientExchangeUserSignal) {
return true;
}
return tokenExchangeFailure && transientNetworkSignal;
}
async function sleep(ms = 0) {
const timeout = Math.max(0, Number(ms) || 0);
if (!timeout) return;
await new Promise((resolve) => setTimeout(resolve, timeout));
}
async function fetchCodex2ApiJson(origin, path, options = {}) {
const controller = new AbortController();
const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 30000));
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': normalizeString(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 executeStep10(state) {
if (getPanelMode(state) === 'codex2api') {
return executeCodex2ApiStep10(state);
}
if (getPanelMode(state) === 'sub2api') {
return executeSub2ApiStep10(state);
}
return executeCpaStep10(state);
}
async function executeCpaStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep, state);
const authLoginStep = resolveAuthLoginStep(platformVerifyStep, state);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}`);
}
if (!state.localhostUrl) {
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}`);
}
if (!state.vpsUrl) {
throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。');
}
if (shouldBypassStep9ForLocalCpa(state)) {
await addStepLog(platformVerifyStep, '检测到本地 CPA,且当前策略为“跳过平台回调验证”,本轮不再重复提交回调地址。', 'info');
await completeNodeFromBackground(state?.nodeId || 'platform-verify', {
localhostUrl: state.localhostUrl,
verifiedStatus: 'local-auto',
});
return;
}
const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep, state);
const expectedState = normalizeString(state.cpaOAuthState);
if (expectedState && expectedState !== callback.state) {
throw new Error(`CPA 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}`);
}
const managementKey = normalizeString(state.vpsPassword);
if (!managementKey) {
throw new Error('尚未配置 CPA 管理密钥,请先在侧边栏填写。');
}
await addStepLog(platformVerifyStep, '正在通过 CPA 管理接口提交回调地址...');
try {
const origin = normalizeString(state.cpaManagementOrigin) || deriveCpaManagementOrigin(state.vpsUrl);
const result = await fetchCpaManagementJson(origin, '/v0/management/oauth-callback', {
method: 'POST',
managementKey,
body: {
provider: 'codex',
redirect_url: callback.url,
},
});
const verifiedStatus = normalizeString(result?.message)
|| normalizeString(result?.status_message)
|| 'CPA 已通过接口提交回调';
await addStepLog(platformVerifyStep, verifiedStatus, 'ok');
await completeNodeFromBackground(state?.nodeId || 'platform-verify', {
localhostUrl: callback.url,
verifiedStatus,
});
} catch (error) {
const reason = normalizeString(error?.message) || 'unknown error';
await addStepLog(platformVerifyStep, `CPA 接口提交失败:${reason}`, 'error');
throw error;
}
}
async function executeCodex2ApiStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
const confirmOauthStep = resolveConfirmOauthStep(platformVerifyStep, state);
const authLoginStep = resolveAuthLoginStep(platformVerifyStep, state);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}`);
}
if (!state.localhostUrl) {
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}`);
}
if (!state.codex2apiSessionId) {
throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${authLoginStep}`);
}
if (!normalizeString(state.codex2apiAdminKey)) {
throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。');
}
const callback = parseLocalhostCallback(state.localhostUrl, platformVerifyStep, state);
const expectedState = normalizeString(state.codex2apiOAuthState);
if (expectedState && expectedState !== callback.state) {
throw new Error(`Codex2API 回调 state 与当前授权会话不匹配,请重新执行步骤 ${authLoginStep}`);
}
const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl);
const origin = new URL(codex2apiUrl).origin;
await addStepLog(platformVerifyStep, '正在向 Codex2API 提交回调并创建账号...');
const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/exchange-code', {
adminKey: state.codex2apiAdminKey,
method: 'POST',
body: {
session_id: state.codex2apiSessionId,
code: callback.code,
state: callback.state,
},
});
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
await addStepLog(platformVerifyStep, verifiedStatus, 'ok');
await completeNodeFromBackground(state?.nodeId || 'platform-verify', {
localhostUrl: callback.url,
verifiedStatus,
});
}
async function executeSub2ApiStep10(state) {
const platformVerifyStep = resolvePlatformVerifyStep(state);
const visibleStep = platformVerifyStep;
const confirmOauthStep = resolveConfirmOauthStep(visibleStep, state);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error(`步骤 ${confirmOauthStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmOauthStep}`);
}
if (!state.localhostUrl) {
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmOauthStep}`);
}
if (!state.sub2apiSessionId) {
throw new Error('缺少 SUB2API 会话信息,请重新执行步骤 1。');
}
if (!state.sub2apiEmail) {
throw new Error('尚未配置 SUB2API 登录邮箱,请先在侧边栏填写。');
}
if (!state.sub2apiPassword) {
throw new Error('尚未配置 SUB2API 登录密码,请先在侧边栏填写。');
}
const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl);
if (!sub2apiUrl) {
throw new Error('SUB2API URL is not configured. Please fill it in the side panel first.');
}
const api = getSub2ApiApi();
const maxExchangeAttempts = 3;
let lastError = null;
for (let attempt = 1; attempt <= maxExchangeAttempts; attempt += 1) {
try {
const result = await api.submitOpenAiCallback({
...state,
visibleStep,
sub2apiUrl,
}, {
visibleStep,
logLabel: `步骤 ${visibleStep}`,
logOptions: { step: visibleStep, stepKey: 'platform-verify' },
timeoutMs: SUB2API_STEP9_RESPONSE_TIMEOUT_MS,
});
await completeNodeFromBackground(state?.nodeId || 'platform-verify', result);
return;
} catch (error) {
lastError = error;
if (!isSub2ApiTransientExchangeError(error) || attempt >= maxExchangeAttempts) {
throw error;
}
await addLog(
`SUB2API 回调交换出现临时网络波动(${error.message}),正在重试 ${attempt + 1}/${maxExchangeAttempts}...`,
'warn',
{ step: visibleStep, stepKey: 'platform-verify' }
);
await sleep(1200 * attempt);
}
}
if (lastError) {
throw lastError;
}
}
return {
executeCpaStep10,
executeCodex2ApiStep10,
executeStep10,
executeSub2ApiStep10,
};
}
return { createStep10Executor };
});
-69
View File
@@ -1,69 +0,0 @@
(function attachBackgroundPlusReturnConfirm(root, factory) {
root.MultiPageBackgroundPlusReturnConfirm = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPlusReturnConfirmModule() {
const PAYPAL_SOURCE = 'paypal-flow';
const GOPAY_SOURCE = 'gopay-flow';
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PLUS_RETURN_SETTLE_WAIT_MS = 20000;
function createPlusReturnConfirmExecutor(deps = {}) {
const {
addLog,
completeNodeFromBackground,
getTabId,
isTabAlive,
setState,
sleepWithStop,
waitForTabUrlMatchUntilStopped,
} = deps;
async function resolveReturnTabId(state = {}) {
const paypalTabId = await getTabId(PAYPAL_SOURCE);
if (paypalTabId && await isTabAlive(PAYPAL_SOURCE)) {
return paypalTabId;
}
const gopayTabId = await getTabId(GOPAY_SOURCE);
if (gopayTabId && await isTabAlive(GOPAY_SOURCE)) {
return gopayTabId;
}
const checkoutTabId = await getTabId(PLUS_CHECKOUT_SOURCE);
if (checkoutTabId) {
return checkoutTabId;
}
const storedTabId = Number(state.plusCheckoutTabId) || 0;
if (storedTabId) {
return storedTabId;
}
throw new Error('步骤 9:未找到 Plus / PayPal / GoPay 标签页,无法确认订阅回跳。');
}
function isReturnUrl(url = '') {
return /https:\/\/(?:chatgpt\.com|chat\.openai\.com|openai\.com)\//i.test(String(url || ''))
&& !/paypal\.|gopay|gojek|midtrans|xendit|stripe/i.test(String(url || ''));
}
async function executePlusReturnConfirm(state = {}) {
const tabId = await resolveReturnTabId(state);
await addLog('步骤 9:正在等待支付授权后回跳到 ChatGPT / OpenAI 页面...', 'info');
const tab = await waitForTabUrlMatchUntilStopped(tabId, isReturnUrl);
await addLog('步骤 9:已检测到订阅回跳页面,固定等待 20 秒让页面完成加载。', 'info');
await sleepWithStop(PLUS_RETURN_SETTLE_WAIT_MS);
await setState({
plusCheckoutTabId: tabId,
plusReturnUrl: tab?.url || '',
});
await completeNodeFromBackground('plus-checkout-return', {
plusReturnUrl: tab?.url || '',
});
}
return {
executePlusReturnConfirm,
};
}
return {
createPlusReturnConfirmExecutor,
};
});
-53
View File
@@ -1,53 +0,0 @@
(function attachBackgroundStepRegistry(root, factory) {
root.MultiPageBackgroundStepRegistry = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStepRegistryModule() {
function createNodeRegistry(definitions = []) {
const ordered = (Array.isArray(definitions) ? definitions : [])
.map((definition) => ({
flowId: String(definition?.flowId || '').trim(),
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
displayOrder: Number(definition?.displayOrder ?? definition?.order ?? definition?.id),
executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(),
title: String(definition?.title || '').trim(),
execute: definition?.execute,
}))
.filter((definition) => definition.nodeId)
.sort((left, right) => {
const leftOrder = Number.isFinite(left.displayOrder) ? left.displayOrder : 0;
const rightOrder = Number.isFinite(right.displayOrder) ? right.displayOrder : 0;
if (leftOrder !== rightOrder) return leftOrder - rightOrder;
return left.nodeId.localeCompare(right.nodeId);
});
const byId = new Map(ordered.map((definition) => [definition.nodeId, definition]));
function getNodeDefinition(nodeId) {
return byId.get(String(nodeId || '').trim()) || null;
}
function getOrderedNodes() {
return ordered.slice();
}
function executeNode(nodeId, state) {
const definition = getNodeDefinition(nodeId);
if (!definition) {
throw new Error(`Unknown node: ${nodeId}`);
}
if (typeof definition.execute !== 'function') {
throw new Error(`Missing node executor: ${definition.executeKey || definition.nodeId}`);
}
return definition.execute(state);
}
return {
executeNode,
getNodeDefinition,
getOrderedNodes,
};
}
return {
createNodeRegistry,
};
});
-280
View File
@@ -1,280 +0,0 @@
(function attachBackgroundSub2ApiSessionImport(root, factory) {
root.MultiPageBackgroundSub2ApiSessionImport = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundSub2ApiSessionImportModule() {
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
function createSub2ApiSessionImportExecutor(deps = {}) {
const {
addLog: rawAddLog = async () => {},
chrome,
completeNodeFromBackground,
ensureContentScriptReadyOnTabUntilStopped,
getTabId,
isTabAlive,
normalizeSub2ApiUrl = (value) => value,
registerTab,
sendTabMessageUntilStopped,
sleepWithStop = async () => {},
throwIfStopped = () => {},
waitForTabCompleteUntilStopped = async () => {},
DEFAULT_SUB2API_GROUP_NAME = 'codex',
} = deps;
let sub2ApiApi = null;
function addStepLog(step, message, level = 'info') {
return rawAddLog(message, level, {
step,
stepKey: 'sub2api-session-import',
});
}
function getSub2ApiApi() {
if (sub2ApiApi) {
return sub2ApiApi;
}
const factory = deps.createSub2ApiApi
|| self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi;
if (typeof factory !== 'function') {
throw new Error('SUB2API 接口模块未加载,无法导入当前 ChatGPT 会话。');
}
sub2ApiApi = factory({
addLog: rawAddLog,
normalizeSub2ApiUrl,
DEFAULT_SUB2API_GROUP_NAME,
});
return sub2ApiApi;
}
function normalizeString(value = '') {
return String(value || '').trim();
}
function resolveVisibleStep(state = {}) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : 10;
}
function isSupportedChatGptSessionUrl(url = '') {
try {
const parsed = new URL(String(url || ''));
if (!/^https?:$/i.test(parsed.protocol)) {
return false;
}
const hostname = String(parsed.hostname || '').trim().toLowerCase();
return /(^|\.)chatgpt\.com$/.test(hostname)
|| hostname === 'chat.openai.com'
|| /(^|\.)openai\.com$/.test(hostname);
} catch {
return false;
}
}
function getSessionTabHostPriority(url = '') {
try {
const hostname = String(new URL(String(url || '')).hostname || '').trim().toLowerCase();
if (/(^|\.)chatgpt\.com$/.test(hostname)) {
return 0;
}
if (hostname === 'chat.openai.com') {
return 1;
}
if (/(^|\.)openai\.com$/.test(hostname)) {
return 2;
}
} catch {
return Number.POSITIVE_INFINITY;
}
return Number.POSITIVE_INFINITY;
}
function getSessionTabActivityPriority(tab = {}) {
if (tab?.active && tab?.currentWindow) {
return 0;
}
if (tab?.active) {
return 1;
}
return 2;
}
function pickPreferredSessionTab(tabs = []) {
const candidates = (Array.isArray(tabs) ? tabs : [])
.filter((tab) => Number.isInteger(tab?.id) && isSupportedChatGptSessionUrl(tab.url));
if (!candidates.length) {
return null;
}
return candidates.reduce((best, candidate) => {
if (!best) {
return candidate;
}
const candidateHostPriority = getSessionTabHostPriority(candidate.url);
const bestHostPriority = getSessionTabHostPriority(best.url);
if (candidateHostPriority !== bestHostPriority) {
return candidateHostPriority < bestHostPriority ? candidate : best;
}
const candidateActivityPriority = getSessionTabActivityPriority(candidate);
const bestActivityPriority = getSessionTabActivityPriority(best);
if (candidateActivityPriority !== bestActivityPriority) {
return candidateActivityPriority < bestActivityPriority ? candidate : best;
}
const candidateLastAccessed = Number(candidate?.lastAccessed) || 0;
const bestLastAccessed = Number(best?.lastAccessed) || 0;
if (candidateLastAccessed !== bestLastAccessed) {
return candidateLastAccessed > bestLastAccessed ? candidate : best;
}
return Number(candidate.id) < Number(best.id) ? candidate : best;
}, null);
}
async function readSupportedSessionTab(tabId) {
const numericTabId = Number(tabId) || 0;
if (!numericTabId || !chrome?.tabs?.get) {
return null;
}
const tab = await chrome.tabs.get(numericTabId).catch(() => null);
return tab?.id && isSupportedChatGptSessionUrl(tab.url)
? tab
: null;
}
async function findFallbackSessionTab() {
if (!chrome?.tabs?.query) {
return null;
}
const activeTabs = await chrome.tabs.query({ active: true, currentWindow: true }).catch(() => []);
const activeMatch = pickPreferredSessionTab(activeTabs);
const allTabs = await chrome.tabs.query({}).catch(() => []);
const globalMatch = pickPreferredSessionTab(allTabs);
return pickPreferredSessionTab([activeMatch, globalMatch]);
}
async function resolveSessionTabId(state = {}) {
const registeredTabId = typeof getTabId === 'function'
? await getTabId(PLUS_CHECKOUT_SOURCE)
: null;
if (registeredTabId && typeof isTabAlive === 'function' && await isTabAlive(PLUS_CHECKOUT_SOURCE)) {
const registeredTab = await readSupportedSessionTab(registeredTabId);
if (registeredTab?.id) {
return registeredTab.id;
}
}
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
const storedTab = await readSupportedSessionTab(storedTabId);
if (storedTab?.id) {
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, storedTab.id);
}
return storedTab.id;
}
const fallbackTab = await findFallbackSessionTab();
if (fallbackTab?.id) {
if (typeof registerTab === 'function') {
await registerTab(PLUS_CHECKOUT_SOURCE, fallbackTab.id);
}
return fallbackTab.id;
}
throw new Error('未找到可读取 ChatGPT 会话的标签页,请先打开一个已登录的 ChatGPT / OpenAI 页面,或完成当前 Plus 支付链路。');
}
async function getResolvedSessionTab(tabId, visibleStep) {
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
if (!tab?.id) {
throw new Error(`步骤 ${visibleStep}:ChatGPT 会话标签页不存在或已关闭,无法继续导入 SUB2API。`);
}
if (!isSupportedChatGptSessionUrl(tab.url)) {
throw new Error(`步骤 ${visibleStep}:当前标签页不在 ChatGPT / OpenAI 页面,无法读取当前登录会话。`);
}
return tab;
}
async function readCurrentChatGptSession(tabId, visibleStep) {
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 会话页完成加载,再继续读取当前登录会话...`,
});
const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'PLUS_CHECKOUT_GET_STATE',
source: 'background',
payload: {
includeSession: true,
includeAccessToken: true,
},
});
if (sessionResult?.error) {
throw new Error(sessionResult.error);
}
const session = sessionResult?.session && typeof sessionResult.session === 'object' && !Array.isArray(sessionResult.session)
? sessionResult.session
: null;
const accessToken = normalizeString(
sessionResult?.accessToken
|| session?.accessToken
);
if (!session && !accessToken) {
throw new Error(`步骤 ${visibleStep}:未读取到有效的 ChatGPT 会话或 accessToken,请确认当前标签页仍处于已登录状态。`);
}
return {
session,
accessToken,
};
}
async function executeSub2ApiSessionImport(state = {}) {
throwIfStopped();
const visibleStep = resolveVisibleStep(state);
const api = getSub2ApiApi();
await addStepLog(visibleStep, '正在定位当前 ChatGPT 会话页并准备导入 SUB2API...', 'info');
const tabId = await resolveSessionTabId(state);
const tab = await getResolvedSessionTab(tabId, visibleStep);
if (chrome?.tabs?.update) {
await chrome.tabs.update(tab.id, { active: true }).catch(() => {});
}
await addStepLog(visibleStep, '正在读取当前 ChatGPT 登录会话...', 'info');
const sessionState = await readCurrentChatGptSession(tab.id, visibleStep);
throwIfStopped();
const result = await api.importCurrentChatGptSession({
...state,
session: sessionState.session,
accessToken: sessionState.accessToken,
}, {
visibleStep,
logLabel: `步骤 ${visibleStep}`,
logOptions: { step: visibleStep, stepKey: 'sub2api-session-import' },
timeoutMs: 120000,
importTimeoutMs: 120000,
});
await completeNodeFromBackground(state?.nodeId || 'sub2api-session-import', result);
}
return {
executeSub2ApiSessionImport,
isSupportedChatGptSessionUrl,
};
}
return {
createSub2ApiSessionImportExecutor,
};
});
-509
View File
@@ -1,509 +0,0 @@
(function attachBackgroundStep2(root, factory) {
root.MultiPageBackgroundStep2 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep2Module() {
function createStep2Executor(deps = {}) {
const {
addLog,
chrome,
completeNodeFromBackground,
ensureContentScriptReadyOnTab,
ensureSignupAuthEntryPageReady,
ensureSignupEntryPageReady,
ensureSignupPostEmailPageReadyInTab,
ensureSignupPostIdentityPageReadyInTab = ensureSignupPostEmailPageReadyInTab,
getTabId,
isTabAlive,
phoneVerificationHelpers = null,
resolveSignupMethod = () => 'email',
resolveSignupEmailForFlow,
sendToContentScriptResilient,
SIGNUP_PAGE_INJECT_FILES,
waitForTabStableComplete = null,
} = deps;
function getErrorMessage(error) {
return String(typeof error === 'string' ? error : error?.message || '');
}
function isSignupEntryUnavailableErrorMessage(errorLike) {
const message = getErrorMessage(errorLike);
return /未找到可用的邮箱输入入口|当前页面没有可用的注册入口,也不在邮箱\/密码页/i.test(message);
}
function isSignupPhoneEntryUnavailableErrorMessage(errorLike) {
const message = getErrorMessage(errorLike);
return /未找到可用的手机号输入入口|当前页面没有可用的手机号注册入口,也不在密码页/i.test(message);
}
function isRetryableStep2TransportErrorMessage(errorLike) {
const message = getErrorMessage(errorLike);
return /Content script on signup-page did not respond in \d+s|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message);
}
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
const url = String(rawUrl || '').trim();
if (!url) {
return false;
}
try {
const parsed = new URL(url);
const host = String(parsed.hostname || '').toLowerCase();
if (!['chatgpt.com', 'www.chatgpt.com'].includes(host)) {
return false;
}
const path = String(parsed.pathname || '');
if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) {
return false;
}
return true;
} catch {
return false;
}
}
function isReadySignupEntryState(state = '') {
const normalized = String(state || '').trim().toLowerCase();
return normalized === 'entry_home'
|| normalized === 'email_entry'
|| normalized === 'phone_entry'
|| normalized === 'password_page';
}
async function getSignupEntryReadyState(tabId) {
if (!Number.isInteger(tabId) || typeof sendToContentScriptResilient !== 'function') {
return '';
}
try {
const result = await sendToContentScriptResilient('signup-page', {
type: 'ENSURE_SIGNUP_ENTRY_READY',
step: 2,
source: 'background',
payload: {},
}, {
timeoutMs: 12000,
retryDelayMs: 500,
logMessage: '步骤 2:正在检查官网注册入口状态...',
});
if (result?.error) {
return '';
}
return String(result?.state || '').trim().toLowerCase();
} catch {
return '';
}
}
async function isLikelyLoggedInChatgptHomeTab(tabId) {
if (typeof chrome?.tabs?.get !== 'function') {
return false;
}
const readyState = await getSignupEntryReadyState(tabId);
if (isReadySignupEntryState(readyState)) {
return false;
}
const currentUrl = await getTabUrl(tabId);
return isLikelyLoggedInChatgptHomeUrl(currentUrl);
}
async function shouldForceAuthEntryRetry(tabId) {
if (!Number.isInteger(tabId)) {
return false;
}
return isLikelyLoggedInChatgptHomeTab(tabId);
}
async function getTabUrl(tabId) {
if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') {
return '';
}
try {
const tab = await chrome.tabs.get(tabId);
return String(tab?.url || '');
} catch {
return '';
}
}
async function failStep2OnLoggedInSession(tabId, reasonMessage = '') {
if (!(await isLikelyLoggedInChatgptHomeTab(tabId))) {
return false;
}
const reasonText = getErrorMessage(reasonMessage);
const reasonSuffix = reasonText ? `(触发原因:${reasonText}` : '';
const message = `步骤 2:检测到当前停留在已登录 ChatGPT 首页,已阻止自动跳过步骤 3/4/5。请先执行步骤 1 清理会话后重试。${reasonSuffix}`;
await addLog(message, 'error');
throw new Error(message);
}
async function sendSignupIdentity(payload = {}, options = {}) {
const {
timeoutMs = 35000,
retryDelayMs = 700,
logMessage = '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
} = options;
try {
return await sendToContentScriptResilient('signup-page', {
type: 'EXECUTE_NODE',
nodeId: 'submit-signup-email',
step: 2,
source: 'background',
payload,
}, {
timeoutMs,
retryDelayMs,
logMessage,
});
} catch (error) {
return { error: getErrorMessage(error) };
}
}
async function waitForStep2SignupTabToSettle(tabId, logMessage) {
if (!Number.isInteger(tabId) || typeof waitForTabStableComplete !== 'function') {
return null;
}
await addLog(
logMessage || '步骤 2:注册页标签已切换,正在等待页面加载完成并额外稳定 3 秒...',
'info',
{ step: 2, stepKey: 'signup-entry' }
);
return waitForTabStableComplete(tabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 3000,
initialDelayMs: 300,
});
}
async function keepSignupTabWindowInBackgroundForStep2(tabId) {
// Intentionally no-op: the task tab is locked to the selected Chrome
// window by the tab-runtime layer. Step 2 must not focus/raise that
// window while the user is working in another app or browser window.
void tabId;
}
async function ensureSignupPhoneEntryReady(tabId) {
if (!Number.isInteger(tabId)) {
throw new Error('步骤 2:未找到可用的注册页标签,无法切换到手机号注册入口。');
}
const result = await sendToContentScriptResilient('signup-page', {
type: 'ENSURE_SIGNUP_PHONE_ENTRY_READY',
step: 2,
source: 'background',
payload: {},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
logMessage: '步骤 2:正在打开官网注册入口并切换到手机号注册...',
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function submitSignupEmail(resolvedEmail, options = {}) {
return sendSignupIdentity({ email: resolvedEmail }, options);
}
async function submitSignupPhone(phoneNumber, activation, options = {}) {
return sendSignupIdentity({
signupMethod: 'phone',
phoneNumber,
countryId: activation?.countryId ?? null,
countryLabel: String(activation?.countryLabel || '').trim(),
}, {
logMessage: '步骤 2:官网注册入口正在切换,等待手机号注册入口恢复...',
...options,
});
}
async function ensureSignupTabForStep2() {
let signupTabId = await getTabId('signup-page');
if (!signupTabId || !(await isTabAlive('signup-page'))) {
await addLog('步骤 2:未发现可用的注册页标签,正在重新打开 ChatGPT 官网...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
} else {
await chrome.tabs.update(signupTabId, { active: true });
await keepSignupTabWindowInBackgroundForStep2(signupTabId);
await waitForStep2SignupTabToSettle(
signupTabId,
'步骤 2:已切换到注册页标签,正在等待页面加载完成并额外稳定 3 秒...'
);
await ensureContentScriptReadyOnTab('signup-page', signupTabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
timeoutMs: 45000,
retryDelayMs: 900,
logMessage: '步骤 2:注册入口页内容脚本未就绪,正在等待页面恢复...',
});
}
return signupTabId;
}
function normalizeSignupPhoneActivationForStep2(activation) {
if (typeof phoneVerificationHelpers?.normalizeActivation === 'function') {
return phoneVerificationHelpers.normalizeActivation(activation);
}
if (!activation || typeof activation !== 'object' || Array.isArray(activation)) {
return null;
}
const activationId = String(activation.activationId ?? activation.id ?? activation.activation ?? '').trim();
const phoneNumber = String(activation.phoneNumber ?? activation.number ?? activation.phone ?? '').trim();
if (!activationId || !phoneNumber) {
return null;
}
return {
...activation,
activationId,
phoneNumber,
};
}
function getSignupPhoneNumberFromState(state = {}) {
return String(
state?.signupPhoneNumber
|| (String(state?.accountIdentifierType || '').trim().toLowerCase() === 'phone' ? state?.accountIdentifier : '')
|| ''
).trim();
}
async function resolveSignupPhoneForStep2(state = {}) {
const existingActivation = normalizeSignupPhoneActivationForStep2(state?.signupPhoneActivation);
if (existingActivation?.phoneNumber) {
await addLog(`步骤 2:复用当前注册手机号 ${existingActivation.phoneNumber},不重新获取号码。`);
return {
phoneNumber: existingActivation.phoneNumber,
activation: existingActivation,
};
}
const manualPhoneNumber = getSignupPhoneNumberFromState(state);
if (manualPhoneNumber) {
await addLog(`步骤 2:使用手动填写的注册手机号 ${manualPhoneNumber},本轮不会重新获取号码。`, 'warn');
return {
phoneNumber: manualPhoneNumber,
activation: null,
};
}
if (typeof phoneVerificationHelpers?.prepareSignupPhoneActivation !== 'function') {
throw new Error('手机号注册流程不可用:接码模块尚未初始化。');
}
const activation = await phoneVerificationHelpers.prepareSignupPhoneActivation(state);
return {
phoneNumber: activation.phoneNumber,
activation,
};
}
async function executeSignupPhoneEntry(state) {
let signupTabId = await ensureSignupTabForStep2();
if (await shouldForceAuthEntryRetry(signupTabId)) {
await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交手机号。', 'warn');
try {
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
} catch (entryError) {
const entryErrorMessage = getErrorMessage(entryError);
if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) {
return;
}
await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交手机号...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
}
}
try {
await ensureSignupPhoneEntryReady(signupTabId);
} catch (entryError) {
const entryErrorMessage = getErrorMessage(entryError);
if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) {
return;
}
if (
isSignupPhoneEntryUnavailableErrorMessage(entryErrorMessage)
|| isSignupEntryUnavailableErrorMessage(entryErrorMessage)
|| isRetryableStep2TransportErrorMessage(entryErrorMessage)
) {
await addLog('步骤 2:手机号注册入口尚未就绪,正在重新打开官网入口后重试一次...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
await ensureSignupPhoneEntryReady(signupTabId);
} else {
throw entryError;
}
}
const signupPhone = await resolveSignupPhoneForStep2(state);
const { phoneNumber, activation } = signupPhone;
let step2Result = await submitSignupPhone(phoneNumber, activation, {
timeoutMs: 45000,
retryDelayMs: 700,
logMessage: '步骤 2:官网注册入口正在切换,等待手机号注册入口恢复...',
});
if (step2Result?.error) {
const errorMessage = getErrorMessage(step2Result.error);
if (
isSignupPhoneEntryUnavailableErrorMessage(errorMessage)
|| isSignupEntryUnavailableErrorMessage(errorMessage)
|| isRetryableStep2TransportErrorMessage(errorMessage)
) {
await addLog('步骤 2:手机号注册入口不可用或通信超时,正在重新准备手机号注册入口后重试一次...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
await ensureSignupPhoneEntryReady(signupTabId);
step2Result = await submitSignupPhone(phoneNumber, activation, {
timeoutMs: 45000,
retryDelayMs: 700,
logMessage: '步骤 2:手机号注册入口已就绪,正在重新提交手机号...',
});
}
}
if (step2Result?.error) {
const finalErrorMessage = getErrorMessage(step2Result.error);
if (
(isSignupEntryUnavailableErrorMessage(finalErrorMessage)
|| isRetryableStep2TransportErrorMessage(finalErrorMessage))
&& await failStep2OnLoggedInSession(signupTabId, finalErrorMessage)
) {
return;
}
if (activation && typeof phoneVerificationHelpers?.cancelSignupPhoneActivation === 'function') {
await phoneVerificationHelpers.cancelSignupPhoneActivation(state, activation).catch(() => {});
}
throw new Error(finalErrorMessage);
}
await addLog(`步骤 2:手机号 ${phoneNumber} 已提交,正在等待页面加载并确认下一步入口...`);
const landingResult = await ensureSignupPostIdentityPageReadyInTab(signupTabId, 2, {
skipUrlWait: Boolean(step2Result?.alreadyOnPasswordPage),
});
await completeNodeFromBackground('submit-signup-email', {
accountIdentifierType: 'phone',
accountIdentifier: phoneNumber,
signupPhoneNumber: phoneNumber,
signupPhoneActivation: activation || null,
nextSignupState: landingResult?.state || step2Result?.state || 'password_page',
nextSignupUrl: landingResult?.url || step2Result?.url || '',
skippedPasswordStep: landingResult?.state === 'phone_verification_page' || landingResult?.state === 'profile_page',
});
}
async function executeSignupEmailEntry(state) {
const resolvedEmail = await resolveSignupEmailForFlow(state);
let signupTabId = await ensureSignupTabForStep2();
if (await shouldForceAuthEntryRetry(signupTabId)) {
await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交邮箱。', 'warn');
try {
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
} catch (entryError) {
const entryErrorMessage = getErrorMessage(entryError);
if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) {
return;
}
await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交邮箱...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
}
}
let step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 35000,
retryDelayMs: 700,
logMessage: '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
});
if (step2Result?.error) {
const errorMessage = getErrorMessage(step2Result.error);
if (isSignupEntryUnavailableErrorMessage(errorMessage)) {
await addLog('步骤 2:未找到邮箱输入入口,正在切换认证入口页后重试一次...', 'warn');
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 35000,
retryDelayMs: 700,
logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...',
});
if (step2Result?.error) {
const retryErrorMessage = getErrorMessage(step2Result.error);
if (isSignupEntryUnavailableErrorMessage(retryErrorMessage)) {
if (await failStep2OnLoggedInSession(signupTabId, retryErrorMessage)) {
return;
}
await addLog('步骤 2:认证入口仍不可用,正在重新进入官网注册入口再重试一次...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 35000,
retryDelayMs: 700,
logMessage: '步骤 2:重试官网注册入口后正在重新提交邮箱...',
});
}
}
} else if (isRetryableStep2TransportErrorMessage(errorMessage)) {
await addLog('步骤 2:注册入口页通信超时,正在切换认证入口页并重试提交邮箱...', 'warn');
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 45000,
retryDelayMs: 700,
logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...',
});
}
}
if (step2Result?.error) {
const finalErrorMessage = getErrorMessage(step2Result.error);
if (
(isSignupEntryUnavailableErrorMessage(finalErrorMessage)
|| isRetryableStep2TransportErrorMessage(finalErrorMessage))
&& await failStep2OnLoggedInSession(signupTabId, finalErrorMessage)
) {
return;
}
throw new Error(finalErrorMessage);
}
if (!step2Result?.alreadyOnPasswordPage) {
await addLog(`步骤 2:邮箱 ${resolvedEmail} 已提交,正在等待页面加载并确认下一步入口...`);
}
const landingResult = await ensureSignupPostEmailPageReadyInTab(signupTabId, 2, {
skipUrlWait: Boolean(step2Result?.alreadyOnPasswordPage),
});
await completeNodeFromBackground('submit-signup-email', {
email: resolvedEmail,
accountIdentifierType: 'email',
accountIdentifier: resolvedEmail,
nextSignupState: landingResult?.state || 'password_page',
nextSignupUrl: landingResult?.url || step2Result?.url || '',
skippedPasswordStep: landingResult?.state === 'verification_page',
});
}
async function executeStep2(state) {
if (resolveSignupMethod(state) === 'phone') {
return executeSignupPhoneEntry(state);
}
return executeSignupEmailEntry(state);
}
return { executeStep2 };
}
return { createStep2Executor };
});
@@ -1,159 +0,0 @@
(function attachBackgroundStep6(root, factory) {
root.MultiPageBackgroundStep6 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep6Module() {
const DEFAULT_REGISTRATION_SUCCESS_WAIT_MS = 20000;
const STEP6_COOKIE_CLEAR_DOMAINS = [
'chatgpt.com',
'chat.openai.com',
'openai.com',
'auth.openai.com',
'auth0.openai.com',
'accounts.openai.com',
];
const STEP6_COOKIE_CLEAR_ORIGINS = [
'https://chatgpt.com',
'https://chat.openai.com',
'https://auth.openai.com',
'https://auth0.openai.com',
'https://accounts.openai.com',
'https://openai.com',
];
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 () => {},
chrome: chromeApi = globalThis.chrome,
completeNodeFromBackground,
getErrorMessage = (error) => error?.message || String(error || '未知错误'),
registrationSuccessWaitMs = DEFAULT_REGISTRATION_SUCCESS_WAIT_MS,
sleepWithStop = async (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0))),
} = deps;
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(`步骤 6browsingData 补扫 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 waitMs = Math.max(0, Math.floor(Number(registrationSuccessWaitMs) || 0));
if (waitMs > 0) {
await addLog(`步骤 6:等待 ${Math.round(waitMs / 1000)} 秒,确认注册成功并让页面稳定...`, 'info');
await sleepWithStop(waitMs);
}
await clearCookiesIfEnabled(state);
await addLog('步骤 6:注册成功等待完成,准备继续获取 OAuth 链接并登录。', 'ok');
await completeNodeFromBackground('wait-registration-success');
}
return { executeStep6 };
}
return { createStep6Executor };
});
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -242,7 +242,7 @@
const requestTimeoutMs = Math.max(1200, Math.min(5000, timeoutMs));
const result = typeof sendToContentScriptResilient === 'function'
? await sendToContentScriptResilient(
'signup-page',
'openai-auth',
request,
{
timeoutMs: requestTimeoutMs,
@@ -251,7 +251,7 @@
logMessage: `步骤 ${step}:验证码提交后页面正在切换,等待页面恢复并确认授权状态...`,
}
)
: await sendToContentScript('signup-page', request, {
: await sendToContentScript('openai-auth', request, {
responseTimeoutMs: requestTimeoutMs,
});
@@ -516,7 +516,7 @@
async function requestVerificationCodeResend(step, options = {}) {
throwIfStopped();
const signupTabId = await getTabId('signup-page');
const signupTabId = await getTabId('openai-auth');
if (!signupTabId) {
throw new Error('认证页面标签页已关闭,无法重新请求验证码。');
}
@@ -525,7 +525,7 @@
await chrome.tabs.update(signupTabId, { active: true });
throwIfStopped();
const result = await sendToContentScript('signup-page', {
const result = await sendToContentScript('openai-auth', {
type: 'RESEND_VERIFICATION_CODE',
step,
source: 'background',
@@ -1111,7 +1111,7 @@
async function submitVerificationCode(step, code, options = {}) {
const completionStep = getCompletionStep(step, options);
const authLoginStep = completionStep >= 11 ? 10 : 7;
const signupTabId = await getTabId('signup-page');
const signupTabId = await getTabId('openai-auth');
if (!signupTabId) {
throw new Error('认证页面标签页已关闭,无法填写验证码。');
}
@@ -1143,7 +1143,7 @@
const shouldAvoidReplaySubmit = step === 8;
if (typeof sendToContentScriptResilient === 'function' && !shouldAvoidReplaySubmit) {
try {
result = await sendToContentScriptResilient('signup-page', message, {
result = await sendToContentScriptResilient('openai-auth', message, {
timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000),
retryDelayMs: 700,
responseTimeoutMs: baseResponseTimeoutMs,
@@ -1206,7 +1206,7 @@
}
} else if (shouldAvoidReplaySubmit) {
try {
result = await sendToContentScript('signup-page', message, {
result = await sendToContentScript('openai-auth', message, {
responseTimeoutMs: baseResponseTimeoutMs,
});
} catch (err) {
@@ -1255,7 +1255,7 @@
throw err;
}
} else {
result = await sendToContentScript('signup-page', message, {
result = await sendToContentScript('openai-auth', message, {
responseTimeoutMs: baseResponseTimeoutMs,
});
}
-157
View File
@@ -1,157 +0,0 @@
(function attachBackgroundWorkflowEngine(root, factory) {
root.MultiPageBackgroundWorkflowEngine = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundWorkflowEngineModule() {
function createWorkflowEngine(deps = {}) {
const {
defaultFlowId = 'openai',
workflowDefinitions = null,
} = deps;
function normalizeFlowId(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || defaultFlowId;
}
function normalizeNodeId(value = '') {
return String(value || '').trim();
}
function resolveStateFlowId(state = {}) {
return normalizeFlowId(state?.flowId || state?.activeFlowId || defaultFlowId);
}
function getWorkflow(options = {}) {
const flowId = normalizeFlowId(options?.flowId || options?.activeFlowId || defaultFlowId);
if (workflowDefinitions?.getWorkflow) {
return workflowDefinitions.getWorkflow({
...options,
activeFlowId: flowId,
flowId,
});
}
return {
flowId,
workflowVersion: 1,
nodes: [],
nodeIds: [],
};
}
function getWorkflowForState(state = {}) {
return getWorkflow({
...state,
flowId: resolveStateFlowId(state),
activeFlowId: resolveStateFlowId(state),
});
}
function getNodesForState(state = {}) {
return Array.isArray(getWorkflowForState(state).nodes)
? getWorkflowForState(state).nodes
: [];
}
function getNodeIdsForState(state = {}) {
return getNodesForState(state).map((node) => node.nodeId).filter(Boolean);
}
function getNodeById(nodeId, state = {}) {
const normalizedNodeId = normalizeNodeId(nodeId);
if (!normalizedNodeId) {
return null;
}
return getNodesForState(state).find((node) => node.nodeId === normalizedNodeId) || null;
}
function getDisplayOrderForNode(nodeId, state = {}) {
const node = getNodeById(nodeId, state);
return Number.isFinite(Number(node?.displayOrder)) ? Number(node.displayOrder) : null;
}
function getNodeTitle(nodeId, state = {}) {
return getNodeById(nodeId, state)?.title || nodeId || '';
}
function normalizeNodeStatus(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized || 'pending';
}
function buildDefaultNodeStatuses(state = {}) {
return Object.fromEntries(getNodeIdsForState(state).map((nodeId) => [nodeId, 'pending']));
}
function normalizeNodeStatuses(statuses = {}, state = {}) {
const defaults = buildDefaultNodeStatuses(state);
const next = { ...defaults };
if (!statuses || typeof statuses !== 'object' || Array.isArray(statuses)) {
return next;
}
for (const [rawNodeId, rawStatus] of Object.entries(statuses)) {
const nodeId = normalizeNodeId(rawNodeId);
if (!nodeId || !Object.prototype.hasOwnProperty.call(defaults, nodeId)) {
continue;
}
next[nodeId] = normalizeNodeStatus(rawStatus);
}
return next;
}
function isNodeDoneStatus(status = '') {
return ['completed', 'manual_completed', 'skipped'].includes(normalizeNodeStatus(status));
}
function isNodeTerminalStatus(status = '') {
return ['completed', 'manual_completed', 'skipped', 'failed', 'stopped'].includes(normalizeNodeStatus(status));
}
function getRunningNodeIds(statuses = {}, state = {}) {
const normalizedStatuses = normalizeNodeStatuses(statuses, state);
return getNodeIdsForState(state).filter((nodeId) => normalizedStatuses[nodeId] === 'running');
}
function getFirstUnfinishedNodeId(statuses = {}, state = {}) {
const normalizedStatuses = normalizeNodeStatuses(statuses, state);
return getNodeIdsForState(state).find((nodeId) => !isNodeDoneStatus(normalizedStatuses[nodeId])) || '';
}
function hasSavedProgress(statuses = {}, state = {}) {
const normalizedStatuses = normalizeNodeStatuses(statuses, state);
return getNodeIdsForState(state).some((nodeId) => normalizeNodeStatus(normalizedStatuses[nodeId]) !== 'pending');
}
function getNextNodeIds(nodeId, state = {}) {
const node = getNodeById(nodeId, state);
if (!node) {
return [];
}
return Array.isArray(node.next) ? node.next.map(normalizeNodeId).filter(Boolean) : [];
}
return {
buildDefaultNodeStatuses,
getDisplayOrderForNode,
getFirstUnfinishedNodeId,
getNextNodeIds,
getNodeById,
getNodeIdsForState,
getNodesForState,
getNodeTitle,
getRunningNodeIds,
getWorkflow,
getWorkflowForState,
hasSavedProgress,
isNodeDoneStatus,
isNodeTerminalStatus,
normalizeFlowId,
normalizeNodeId,
normalizeNodeStatuses,
normalizeNodeStatus,
resolveStateFlowId,
};
}
return {
createWorkflowEngine,
};
});