feat: lay groundwork for multi-flow registries
This commit is contained in:
+162
-18
@@ -1,6 +1,8 @@
|
|||||||
// background.js — Service Worker: orchestration, state, tab management, message routing
|
// background.js — Service Worker: orchestration, state, tab management, message routing
|
||||||
|
|
||||||
importScripts(
|
importScripts(
|
||||||
|
'shared/source-registry.js',
|
||||||
|
'shared/flow-capabilities.js',
|
||||||
'managed-alias-utils.js',
|
'managed-alias-utils.js',
|
||||||
'mail2925-utils.js',
|
'mail2925-utils.js',
|
||||||
'paypal-utils.js',
|
'paypal-utils.js',
|
||||||
@@ -17,8 +19,11 @@ importScripts(
|
|||||||
'background/ip-proxy-core.js',
|
'background/ip-proxy-core.js',
|
||||||
'background/panel-bridge.js',
|
'background/panel-bridge.js',
|
||||||
'background/registration-email-state.js',
|
'background/registration-email-state.js',
|
||||||
|
'background/runtime-state.js',
|
||||||
'background/generated-email-helpers.js',
|
'background/generated-email-helpers.js',
|
||||||
'background/signup-flow-helpers.js',
|
'background/signup-flow-helpers.js',
|
||||||
|
'background/mail-rule-registry.js',
|
||||||
|
'flows/openai/mail-rules.js',
|
||||||
'background/message-router.js',
|
'background/message-router.js',
|
||||||
'background/verification-flow.js',
|
'background/verification-flow.js',
|
||||||
'background/auto-run-controller.js',
|
'background/auto-run-controller.js',
|
||||||
@@ -80,6 +85,8 @@ const STEP_IDS = Array.from(new Set(ALL_STEP_DEFINITIONS
|
|||||||
.map((definition) => Number(definition?.id))
|
.map((definition) => Number(definition?.id))
|
||||||
.filter(Number.isFinite)))
|
.filter(Number.isFinite)))
|
||||||
.sort((left, right) => left - right);
|
.sort((left, right) => left - right);
|
||||||
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
|
const DEFAULT_STEP_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending']));
|
||||||
const NORMAL_STEP_IDS = NORMAL_STEP_DEFINITIONS
|
const NORMAL_STEP_IDS = NORMAL_STEP_DEFINITIONS
|
||||||
.map((definition) => Number(definition?.id))
|
.map((definition) => Number(definition?.id))
|
||||||
.filter(Number.isFinite)
|
.filter(Number.isFinite)
|
||||||
@@ -205,6 +212,11 @@ const {
|
|||||||
isRecoverableStep9AuthFailure,
|
isRecoverableStep9AuthFailure,
|
||||||
} = self.MultiPageActivationUtils;
|
} = self.MultiPageActivationUtils;
|
||||||
const registrationEmailStateHelpers = self.MultiPageRegistrationEmailState?.createRegistrationEmailStateHelpers?.() || null;
|
const registrationEmailStateHelpers = self.MultiPageRegistrationEmailState?.createRegistrationEmailStateHelpers?.() || null;
|
||||||
|
const runtimeStateHelpers = self.MultiPageBackgroundRuntimeState?.createRuntimeStateHelpers?.({
|
||||||
|
DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
defaultStepStatuses: DEFAULT_STEP_STATUSES,
|
||||||
|
getStepDefinitionForState: (step, state) => getStepDefinitionForState(step, state),
|
||||||
|
}) || null;
|
||||||
const DEFAULT_REGISTRATION_EMAIL_STATE = registrationEmailStateHelpers?.DEFAULT_REGISTRATION_EMAIL_STATE || {
|
const DEFAULT_REGISTRATION_EMAIL_STATE = registrationEmailStateHelpers?.DEFAULT_REGISTRATION_EMAIL_STATE || {
|
||||||
current: '',
|
current: '',
|
||||||
previous: '',
|
previous: '',
|
||||||
@@ -269,6 +281,20 @@ function getPreservedPhoneIdentity(state = {}) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildStateViewWithRuntimeState(state = {}) {
|
||||||
|
if (runtimeStateHelpers?.buildStateView) {
|
||||||
|
return runtimeStateHelpers.buildStateView(state);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStatePatchWithRuntimeState(currentState = {}, updates = {}) {
|
||||||
|
if (runtimeStateHelpers?.buildSessionStatePatch) {
|
||||||
|
return runtimeStateHelpers.buildSessionStatePatch(currentState, updates);
|
||||||
|
}
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
function statePatchHasChanges(state = {}, patch = {}) {
|
function statePatchHasChanges(state = {}, patch = {}) {
|
||||||
return Object.keys(patch).some((key) => JSON.stringify(state?.[key] ?? null) !== JSON.stringify(patch[key] ?? null));
|
return Object.keys(patch).some((key) => JSON.stringify(state?.[key] ?? null) !== JSON.stringify(patch[key] ?? null));
|
||||||
}
|
}
|
||||||
@@ -844,8 +870,13 @@ const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
|
|||||||
const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000;
|
const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000;
|
||||||
|
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
activeRunId: '',
|
||||||
|
currentNodeId: '',
|
||||||
|
nodeStatuses: {},
|
||||||
currentStep: 0, // 当前流程执行到的步骤编号。
|
currentStep: 0, // 当前流程执行到的步骤编号。
|
||||||
stepStatuses: Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending'])),
|
stepStatuses: { ...DEFAULT_STEP_STATUSES },
|
||||||
|
runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null,
|
||||||
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
||||||
oauthUrl: null, // 运行时抓取到的 OAuth 地址,不要手动预填。
|
oauthUrl: null, // 运行时抓取到的 OAuth 地址,不要手动预填。
|
||||||
resolvedSignupMethod: null, // 当前自动轮次冻结后的实际注册方式。
|
resolvedSignupMethod: null, // 当前自动轮次冻结后的实际注册方式。
|
||||||
@@ -1320,7 +1351,49 @@ function normalizeSignupMethod(value = '') {
|
|||||||
: 'email';
|
: 'email';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFlowCapabilityRegistry() {
|
||||||
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
|
if (typeof flowCapabilityRegistry !== 'undefined' && flowCapabilityRegistry) {
|
||||||
|
return flowCapabilityRegistry;
|
||||||
|
}
|
||||||
|
return rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
|
||||||
|
const registry = getFlowCapabilityRegistry();
|
||||||
|
if (!registry?.resolveSidepanelCapabilities) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: options?.activeFlowId ?? state?.activeFlowId,
|
||||||
|
panelMode: options?.panelMode ?? state?.panelMode,
|
||||||
|
signupMethod: options?.signupMethod ?? state?.signupMethod,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function canUsePhoneSignup(state = {}) {
|
function canUsePhoneSignup(state = {}) {
|
||||||
|
const capabilityState = typeof resolveCurrentFlowCapabilities === 'function'
|
||||||
|
? resolveCurrentFlowCapabilities(state)
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: state?.activeFlowId,
|
||||||
|
panelMode: state?.panelMode,
|
||||||
|
signupMethod: state?.signupMethod,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
if (capabilityState && typeof capabilityState.canUsePhoneSignup === 'boolean') {
|
||||||
|
return capabilityState.canUsePhoneSignup;
|
||||||
|
}
|
||||||
return Boolean(state?.phoneVerificationEnabled)
|
return Boolean(state?.phoneVerificationEnabled)
|
||||||
&& !Boolean(state?.plusModeEnabled)
|
&& !Boolean(state?.plusModeEnabled)
|
||||||
&& !Boolean(state?.contributionMode);
|
&& !Boolean(state?.contributionMode);
|
||||||
@@ -1332,9 +1405,26 @@ function resolveSignupMethod(state = {}) {
|
|||||||
return normalizeSignupMethod(frozenMethod);
|
return normalizeSignupMethod(frozenMethod);
|
||||||
}
|
}
|
||||||
const method = normalizeSignupMethod(state?.signupMethod);
|
const method = normalizeSignupMethod(state?.signupMethod);
|
||||||
return method === SIGNUP_METHOD_PHONE && canUsePhoneSignup(state)
|
const capabilityState = typeof resolveCurrentFlowCapabilities === 'function'
|
||||||
? SIGNUP_METHOD_PHONE
|
? resolveCurrentFlowCapabilities(state, { signupMethod: method })
|
||||||
: SIGNUP_METHOD_EMAIL;
|
: (() => {
|
||||||
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: state?.activeFlowId,
|
||||||
|
panelMode: state?.panelMode,
|
||||||
|
signupMethod: method,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
if (capabilityState?.effectiveSignupMethod) {
|
||||||
|
return normalizeSignupMethod(capabilityState.effectiveSignupMethod);
|
||||||
|
}
|
||||||
|
return method === SIGNUP_METHOD_PHONE && canUsePhoneSignup(state) ? SIGNUP_METHOD_PHONE : SIGNUP_METHOD_EMAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureResolvedSignupMethodForRun(options = {}) {
|
async function ensureResolvedSignupMethodForRun(options = {}) {
|
||||||
@@ -2762,7 +2852,9 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
};
|
};
|
||||||
if (Object.prototype.hasOwnProperty.call(payload, 'phoneVerificationEnabled')
|
if (Object.prototype.hasOwnProperty.call(payload, 'phoneVerificationEnabled')
|
||||||
|| Object.prototype.hasOwnProperty.call(payload, 'plusModeEnabled')
|
|| Object.prototype.hasOwnProperty.call(payload, 'plusModeEnabled')
|
||||||
|| Object.prototype.hasOwnProperty.call(payload, 'signupMethod')) {
|
|| Object.prototype.hasOwnProperty.call(payload, 'signupMethod')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(payload, 'panelMode')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(payload, 'activeFlowId')) {
|
||||||
payload.signupMethod = resolveSignupMethod(nextSignupConstraintState);
|
payload.signupMethod = resolveSignupMethod(nextSignupConstraintState);
|
||||||
}
|
}
|
||||||
if (payload.ipProxyServiceProfiles) {
|
if (payload.ipProxyServiceProfiles) {
|
||||||
@@ -2838,7 +2930,13 @@ async function getState() {
|
|||||||
getPersistedAliasState(),
|
getPersistedAliasState(),
|
||||||
accountRunHistoryHelpers?.getPersistedAccountRunHistory?.() || [],
|
accountRunHistoryHelpers?.getPersistedAccountRunHistory?.() || [],
|
||||||
]);
|
]);
|
||||||
return { ...DEFAULT_STATE, ...persistedSettings, ...persistedAliasState, ...state, accountRunHistory };
|
return buildStateViewWithRuntimeState({
|
||||||
|
...DEFAULT_STATE,
|
||||||
|
...persistedSettings,
|
||||||
|
...persistedAliasState,
|
||||||
|
...state,
|
||||||
|
accountRunHistory,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initializeSessionStorageAccess() {
|
async function initializeSessionStorageAccess() {
|
||||||
@@ -2857,19 +2955,24 @@ async function initializeSessionStorageAccess() {
|
|||||||
async function setState(updates) {
|
async function setState(updates) {
|
||||||
console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200));
|
console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200));
|
||||||
if (Object.keys(updates || {}).length > 0) {
|
if (Object.keys(updates || {}).length > 0) {
|
||||||
await chrome.storage.session.set(updates);
|
const currentSessionState = await chrome.storage.session.get(null);
|
||||||
|
const sessionUpdates = buildStatePatchWithRuntimeState({
|
||||||
|
...DEFAULT_STATE,
|
||||||
|
...currentSessionState,
|
||||||
|
}, updates);
|
||||||
|
await chrome.storage.session.set(sessionUpdates);
|
||||||
const persistentAliasUpdates = {};
|
const persistentAliasUpdates = {};
|
||||||
if (Object.prototype.hasOwnProperty.call(updates, 'manualAliasUsage')) {
|
if (Object.prototype.hasOwnProperty.call(sessionUpdates, 'manualAliasUsage')) {
|
||||||
persistentAliasUpdates.manualAliasUsage = normalizeBooleanMap(updates.manualAliasUsage);
|
persistentAliasUpdates.manualAliasUsage = normalizeBooleanMap(sessionUpdates.manualAliasUsage);
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(updates, 'preservedAliases')) {
|
if (Object.prototype.hasOwnProperty.call(sessionUpdates, 'preservedAliases')) {
|
||||||
persistentAliasUpdates.preservedAliases = normalizeBooleanMap(updates.preservedAliases);
|
persistentAliasUpdates.preservedAliases = normalizeBooleanMap(sessionUpdates.preservedAliases);
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(updates, 'icloudAliasCache')) {
|
if (Object.prototype.hasOwnProperty.call(sessionUpdates, 'icloudAliasCache')) {
|
||||||
persistentAliasUpdates.icloudAliasCache = normalizeIcloudAliasCacheList(updates.icloudAliasCache);
|
persistentAliasUpdates.icloudAliasCache = normalizeIcloudAliasCacheList(sessionUpdates.icloudAliasCache);
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(updates, 'icloudAliasCacheAt')) {
|
if (Object.prototype.hasOwnProperty.call(sessionUpdates, 'icloudAliasCacheAt')) {
|
||||||
persistentAliasUpdates.icloudAliasCacheAt = Math.max(0, Number(updates.icloudAliasCacheAt) || 0);
|
persistentAliasUpdates.icloudAliasCacheAt = Math.max(0, Number(sessionUpdates.icloudAliasCacheAt) || 0);
|
||||||
}
|
}
|
||||||
if (Object.keys(persistentAliasUpdates).length > 0) {
|
if (Object.keys(persistentAliasUpdates).length > 0) {
|
||||||
await chrome.storage.local.set(persistentAliasUpdates);
|
await chrome.storage.local.set(persistentAliasUpdates);
|
||||||
@@ -3455,7 +3558,7 @@ async function resetState() {
|
|||||||
? prev.freeReusablePhoneActivation
|
? prev.freeReusablePhoneActivation
|
||||||
: null;
|
: null;
|
||||||
await chrome.storage.session.clear();
|
await chrome.storage.session.clear();
|
||||||
await chrome.storage.session.set({
|
const resetPayload = buildStatePatchWithRuntimeState({}, {
|
||||||
...DEFAULT_STATE,
|
...DEFAULT_STATE,
|
||||||
...persistedSettings,
|
...persistedSettings,
|
||||||
...persistedAliasState,
|
...persistedAliasState,
|
||||||
@@ -3485,6 +3588,7 @@ async function resetState() {
|
|||||||
? Number(prev.automationWindowId)
|
? Number(prev.automationWindowId)
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
await chrome.storage.session.set(resetPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7210,7 +7314,7 @@ function isSignupEntryHost(hostname = '') {
|
|||||||
if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupEntryHost) {
|
if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupEntryHost) {
|
||||||
return navigationUtils.isSignupEntryHost(hostname);
|
return navigationUtils.isSignupEntryHost(hostname);
|
||||||
}
|
}
|
||||||
return ['chatgpt.com', 'chat.openai.com'].includes(hostname);
|
return ['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
|
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
|
||||||
@@ -7294,6 +7398,7 @@ function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
|||||||
if (!candidate) return false;
|
if (!candidate) return false;
|
||||||
const reference = parseUrlSafely(referenceUrl);
|
const reference = parseUrlSafely(referenceUrl);
|
||||||
switch (source) {
|
switch (source) {
|
||||||
|
case 'openai-auth':
|
||||||
case 'signup-page':
|
case 'signup-page':
|
||||||
return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
|
return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
|
||||||
case 'duck-mail':
|
case 'duck-mail':
|
||||||
@@ -7304,6 +7409,8 @@ function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
|||||||
return is163MailHost(candidate.hostname);
|
return is163MailHost(candidate.hostname);
|
||||||
case 'gmail-mail':
|
case 'gmail-mail':
|
||||||
return candidate.hostname === 'mail.google.com';
|
return candidate.hostname === 'mail.google.com';
|
||||||
|
case 'icloud-mail':
|
||||||
|
return candidate.hostname === 'www.icloud.com' || candidate.hostname === 'www.icloud.com.cn';
|
||||||
case 'inbucket-mail':
|
case 'inbucket-mail':
|
||||||
return Boolean(reference) && candidate.origin === reference.origin && candidate.pathname.startsWith('/m/');
|
return Boolean(reference) && candidate.origin === reference.origin && candidate.pathname.startsWith('/m/');
|
||||||
case 'mail-2925':
|
case 'mail-2925':
|
||||||
@@ -7314,11 +7421,24 @@ function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
|||||||
return Boolean(reference)
|
return Boolean(reference)
|
||||||
&& candidate.origin === reference.origin
|
&& candidate.origin === reference.origin
|
||||||
&& (candidate.pathname.startsWith('/admin/accounts') || candidate.pathname.startsWith('/login') || candidate.pathname === '/');
|
&& (candidate.pathname.startsWith('/admin/accounts') || candidate.pathname.startsWith('/login') || candidate.pathname === '/');
|
||||||
|
case 'codex2api-panel':
|
||||||
|
return Boolean(reference)
|
||||||
|
&& candidate.origin === reference.origin
|
||||||
|
&& (candidate.pathname.startsWith('/admin/accounts') || candidate.pathname === '/admin' || candidate.pathname === '/');
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sourcesMatch(leftSource, rightSource) {
|
||||||
|
if (sourceRegistry?.resolveCanonicalSource) {
|
||||||
|
const left = sourceRegistry.resolveCanonicalSource(leftSource);
|
||||||
|
const right = sourceRegistry.resolveCanonicalSource(rightSource);
|
||||||
|
return Boolean(left && right && left === right);
|
||||||
|
}
|
||||||
|
return String(leftSource || '').trim() === String(rightSource || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
async function rememberSourceLastUrl(source, url) {
|
async function rememberSourceLastUrl(source, url) {
|
||||||
return tabRuntime.rememberSourceLastUrl(source, url);
|
return tabRuntime.rememberSourceLastUrl(source, url);
|
||||||
}
|
}
|
||||||
@@ -7399,7 +7519,7 @@ async function ensureContentScriptReadyOnTab(source, tabId, options = {}) {
|
|||||||
|
|
||||||
function isContentScriptReadyPong(source, pong) {
|
function isContentScriptReadyPong(source, pong) {
|
||||||
if (!pong?.ok) return false;
|
if (!pong?.ok) return false;
|
||||||
if (pong.source && pong.source !== source) return false;
|
if (pong.source && !sourcesMatch(pong.source, source)) return false;
|
||||||
if (source === 'plus-checkout') {
|
if (source === 'plus-checkout') {
|
||||||
return Boolean(pong.plusCheckoutReady);
|
return Boolean(pong.plusCheckoutReady);
|
||||||
}
|
}
|
||||||
@@ -7590,11 +7710,13 @@ function getSourceLabel(source) {
|
|||||||
return loggingStatus.getSourceLabel(source);
|
return loggingStatus.getSourceLabel(source);
|
||||||
}
|
}
|
||||||
const labels = {
|
const labels = {
|
||||||
|
'openai-auth': '认证页',
|
||||||
'gmail-mail': 'Gmail 邮箱',
|
'gmail-mail': 'Gmail 邮箱',
|
||||||
'sidepanel': '侧边栏',
|
'sidepanel': '侧边栏',
|
||||||
'signup-page': '认证页',
|
'signup-page': '认证页',
|
||||||
'vps-panel': 'CPA 面板',
|
'vps-panel': 'CPA 面板',
|
||||||
'sub2api-panel': 'SUB2API 后台',
|
'sub2api-panel': 'SUB2API 后台',
|
||||||
|
'codex2api-panel': 'Codex2API 后台',
|
||||||
'qq-mail': 'QQ 邮箱',
|
'qq-mail': 'QQ 邮箱',
|
||||||
'mail-163': '163 邮箱',
|
'mail-163': '163 邮箱',
|
||||||
'mail-2925': '2925 邮箱',
|
'mail-2925': '2925 邮箱',
|
||||||
@@ -7606,6 +7728,8 @@ function getSourceLabel(source) {
|
|||||||
'cloudmail': 'Cloud Mail',
|
'cloudmail': 'Cloud Mail',
|
||||||
'plus-checkout': 'Plus Checkout',
|
'plus-checkout': 'Plus Checkout',
|
||||||
'paypal-flow': 'PayPal 授权页',
|
'paypal-flow': 'PayPal 授权页',
|
||||||
|
'gopay-flow': 'GoPay 授权页',
|
||||||
|
'unknown-source': '未知来源',
|
||||||
};
|
};
|
||||||
return labels[source] || source || '未知来源';
|
return labels[source] || source || '未知来源';
|
||||||
}
|
}
|
||||||
@@ -7659,10 +7783,16 @@ function getStepFetchNetworkRetryPolicy(step) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sourceRegistry = self.MultiPageSourceRegistry?.createSourceRegistry?.() || null;
|
||||||
|
const flowCapabilityRegistry = self.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
}) || null;
|
||||||
|
|
||||||
const navigationUtils = self.MultiPageBackgroundNavigationUtils?.createNavigationUtils({
|
const navigationUtils = self.MultiPageBackgroundNavigationUtils?.createNavigationUtils({
|
||||||
DEFAULT_CODEX2API_URL,
|
DEFAULT_CODEX2API_URL,
|
||||||
DEFAULT_SUB2API_URL,
|
DEFAULT_SUB2API_URL,
|
||||||
normalizeLocalCpaStep9Mode,
|
normalizeLocalCpaStep9Mode,
|
||||||
|
sourceRegistry,
|
||||||
});
|
});
|
||||||
|
|
||||||
const loggingStatus = self.MultiPageBackgroundLoggingStatus?.createLoggingStatus({
|
const loggingStatus = self.MultiPageBackgroundLoggingStatus?.createLoggingStatus({
|
||||||
@@ -7672,6 +7802,7 @@ const loggingStatus = self.MultiPageBackgroundLoggingStatus?.createLoggingStatus
|
|||||||
isRecoverableStep9AuthFailure,
|
isRecoverableStep9AuthFailure,
|
||||||
LOG_PREFIX,
|
LOG_PREFIX,
|
||||||
setState,
|
setState,
|
||||||
|
sourceRegistry,
|
||||||
STOP_ERROR_MESSAGE,
|
STOP_ERROR_MESSAGE,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -7684,6 +7815,7 @@ const tabRuntime = self.MultiPageBackgroundTabRuntime?.createTabRuntime({
|
|||||||
isRetryableContentScriptTransportError,
|
isRetryableContentScriptTransportError,
|
||||||
LOG_PREFIX,
|
LOG_PREFIX,
|
||||||
matchesSourceUrlFamily,
|
matchesSourceUrlFamily,
|
||||||
|
sourceRegistry,
|
||||||
setState,
|
setState,
|
||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
STOP_ERROR_MESSAGE,
|
STOP_ERROR_MESSAGE,
|
||||||
@@ -11251,8 +11383,20 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
|
|||||||
waitForTabStableComplete,
|
waitForTabStableComplete,
|
||||||
waitForTabUrlMatch,
|
waitForTabUrlMatch,
|
||||||
});
|
});
|
||||||
|
const openAiMailRules = self.MultiPageOpenAiMailRules?.createOpenAiMailRules({
|
||||||
|
getHotmailVerificationRequestTimestamp,
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS,
|
||||||
|
});
|
||||||
|
const mailRuleRegistry = self.MultiPageBackgroundMailRuleRegistry?.createMailRuleRegistry({
|
||||||
|
defaultFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
flowBuilders: {
|
||||||
|
openai: openAiMailRules,
|
||||||
|
},
|
||||||
|
});
|
||||||
const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.createVerificationFlowHelpers({
|
const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.createVerificationFlowHelpers({
|
||||||
addLog,
|
addLog,
|
||||||
|
buildVerificationPollPayload: mailRuleRegistry?.buildVerificationPollPayload,
|
||||||
chrome,
|
chrome,
|
||||||
closeConflictingTabsForSource,
|
closeConflictingTabsForSource,
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
|
|||||||
@@ -9,16 +9,22 @@
|
|||||||
isRecoverableStep9AuthFailure,
|
isRecoverableStep9AuthFailure,
|
||||||
LOG_PREFIX,
|
LOG_PREFIX,
|
||||||
setState,
|
setState,
|
||||||
|
sourceRegistry = null,
|
||||||
STOP_ERROR_MESSAGE,
|
STOP_ERROR_MESSAGE,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
function getSourceLabel(source) {
|
function getSourceLabel(source) {
|
||||||
|
if (sourceRegistry?.getSourceLabel) {
|
||||||
|
return sourceRegistry.getSourceLabel(source);
|
||||||
|
}
|
||||||
const labels = {
|
const labels = {
|
||||||
|
'openai-auth': '认证页',
|
||||||
'gmail-mail': 'Gmail 邮箱',
|
'gmail-mail': 'Gmail 邮箱',
|
||||||
'sidepanel': '侧边栏',
|
'sidepanel': '侧边栏',
|
||||||
'signup-page': '认证页',
|
'signup-page': '认证页',
|
||||||
'vps-panel': 'CPA 面板',
|
'vps-panel': 'CPA 面板',
|
||||||
'sub2api-panel': 'SUB2API 后台',
|
'sub2api-panel': 'SUB2API 后台',
|
||||||
|
'codex2api-panel': 'Codex2API 后台',
|
||||||
'qq-mail': 'QQ 邮箱',
|
'qq-mail': 'QQ 邮箱',
|
||||||
'mail-163': '163 邮箱',
|
'mail-163': '163 邮箱',
|
||||||
'mail-2925': '2925 邮箱',
|
'mail-2925': '2925 邮箱',
|
||||||
@@ -28,6 +34,10 @@
|
|||||||
'luckmail-api': 'LuckMail(API 购邮)',
|
'luckmail-api': 'LuckMail(API 购邮)',
|
||||||
'cloudflare-temp-email': 'Cloudflare Temp Email',
|
'cloudflare-temp-email': 'Cloudflare Temp Email',
|
||||||
'cloudmail': 'Cloud Mail',
|
'cloudmail': 'Cloud Mail',
|
||||||
|
'plus-checkout': 'Plus Checkout',
|
||||||
|
'paypal-flow': 'PayPal 授权页',
|
||||||
|
'gopay-flow': 'GoPay 授权页',
|
||||||
|
'unknown-source': '未知来源',
|
||||||
};
|
};
|
||||||
return labels[source] || source || '未知来源';
|
return labels[source] || source || '未知来源';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
(function attachBackgroundMailRuleRegistry(root, factory) {
|
||||||
|
root.MultiPageBackgroundMailRuleRegistry = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundMailRuleRegistryModule() {
|
||||||
|
function createMailRuleRegistry(deps = {}) {
|
||||||
|
const {
|
||||||
|
defaultFlowId = 'openai',
|
||||||
|
flowBuilders = {},
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function resolveFlowId(state = {}) {
|
||||||
|
const activeFlowId = String(state?.activeFlowId || '').trim();
|
||||||
|
return activeFlowId || String(defaultFlowId || '').trim() || 'openai';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlowBuilder(flowId) {
|
||||||
|
const normalizedFlowId = String(flowId || '').trim();
|
||||||
|
return normalizedFlowId ? flowBuilders[normalizedFlowId] || null : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVerificationMailRule(step, state = {}) {
|
||||||
|
const flowId = resolveFlowId(state);
|
||||||
|
const flowBuilder = getFlowBuilder(flowId);
|
||||||
|
if (!flowBuilder || typeof flowBuilder.getRuleDefinition !== 'function') {
|
||||||
|
throw new Error(`未找到 flow=${flowId} 的邮件规则定义。`);
|
||||||
|
}
|
||||||
|
return flowBuilder.getRuleDefinition(step, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVerificationPollPayload(step, state = {}, overrides = {}) {
|
||||||
|
const flowId = resolveFlowId(state);
|
||||||
|
const flowBuilder = getFlowBuilder(flowId);
|
||||||
|
if (!flowBuilder || typeof flowBuilder.buildVerificationPollPayload !== 'function') {
|
||||||
|
throw new Error(`未找到 flow=${flowId} 的邮件轮询规则构造器。`);
|
||||||
|
}
|
||||||
|
return flowBuilder.buildVerificationPollPayload(step, state, overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildVerificationPollPayload,
|
||||||
|
getVerificationMailRule,
|
||||||
|
resolveFlowId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createMailRuleRegistry,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -51,11 +51,27 @@
|
|||||||
getStepIdsForState,
|
getStepIdsForState,
|
||||||
getLastStepIdForState,
|
getLastStepIdForState,
|
||||||
normalizeSignupMethod = (value = '') => String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email',
|
normalizeSignupMethod = (value = '') => String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email',
|
||||||
canUsePhoneSignup = (state = {}) => Boolean(state?.phoneVerificationEnabled)
|
canUsePhoneSignup = (state = {}) => {
|
||||||
&& !Boolean(state?.plusModeEnabled)
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
&& !Boolean(state?.contributionMode),
|
const capabilityRegistry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: 'openai',
|
||||||
|
}) || null;
|
||||||
|
if (capabilityRegistry?.canUsePhoneSignup) {
|
||||||
|
return capabilityRegistry.canUsePhoneSignup(state);
|
||||||
|
}
|
||||||
|
return Boolean(state?.phoneVerificationEnabled)
|
||||||
|
&& !Boolean(state?.plusModeEnabled)
|
||||||
|
&& !Boolean(state?.contributionMode);
|
||||||
|
},
|
||||||
resolveSignupMethod = (state = {}) => {
|
resolveSignupMethod = (state = {}) => {
|
||||||
const method = normalizeSignupMethod(state?.signupMethod);
|
const method = normalizeSignupMethod(state?.signupMethod);
|
||||||
|
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||||
|
const capabilityRegistry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: 'openai',
|
||||||
|
}) || null;
|
||||||
|
if (capabilityRegistry?.resolveSignupMethod) {
|
||||||
|
return capabilityRegistry.resolveSignupMethod(state, method);
|
||||||
|
}
|
||||||
return method === 'phone' && canUsePhoneSignup(state) ? 'phone' : 'email';
|
return method === 'phone' && canUsePhoneSignup(state) ? 'phone' : 'email';
|
||||||
},
|
},
|
||||||
getTabId,
|
getTabId,
|
||||||
@@ -999,6 +1015,8 @@
|
|||||||
Object.prototype.hasOwnProperty.call(updates, 'phoneVerificationEnabled')
|
Object.prototype.hasOwnProperty.call(updates, 'phoneVerificationEnabled')
|
||||||
|| Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|
|| Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|
||||||
|| Object.prototype.hasOwnProperty.call(updates, 'signupMethod')
|
|| Object.prototype.hasOwnProperty.call(updates, 'signupMethod')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(updates, 'panelMode')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(updates, 'activeFlowId')
|
||||||
) {
|
) {
|
||||||
updates.signupMethod = resolveSignupMethod(nextSignupState);
|
updates.signupMethod = resolveSignupMethod(nextSignupState);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
DEFAULT_CODEX2API_URL,
|
DEFAULT_CODEX2API_URL,
|
||||||
DEFAULT_SUB2API_URL,
|
DEFAULT_SUB2API_URL,
|
||||||
normalizeLocalCpaStep9Mode,
|
normalizeLocalCpaStep9Mode,
|
||||||
|
sourceRegistry = null,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
function parseUrlSafely(rawUrl) {
|
function parseUrlSafely(rawUrl) {
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isSignupEntryHost(hostname = '') {
|
function isSignupEntryHost(hostname = '') {
|
||||||
return ['chatgpt.com', 'chat.openai.com'].includes(hostname);
|
return ['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSignupPasswordPageUrl(rawUrl) {
|
function isSignupPasswordPageUrl(rawUrl) {
|
||||||
@@ -117,12 +118,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
||||||
|
if (sourceRegistry?.matchesSourceUrlFamily) {
|
||||||
|
return sourceRegistry.matchesSourceUrlFamily(source, candidateUrl, referenceUrl);
|
||||||
|
}
|
||||||
const candidate = parseUrlSafely(candidateUrl);
|
const candidate = parseUrlSafely(candidateUrl);
|
||||||
if (!candidate) return false;
|
if (!candidate) return false;
|
||||||
|
|
||||||
const reference = parseUrlSafely(referenceUrl);
|
const reference = parseUrlSafely(referenceUrl);
|
||||||
|
|
||||||
switch (source) {
|
switch (source) {
|
||||||
|
case 'openai-auth':
|
||||||
case 'signup-page':
|
case 'signup-page':
|
||||||
return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
|
return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
|
||||||
case 'duck-mail':
|
case 'duck-mail':
|
||||||
|
|||||||
@@ -0,0 +1,484 @@
|
|||||||
|
(function attachBackgroundRuntimeState(root, factory) {
|
||||||
|
root.MultiPageBackgroundRuntimeState = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundRuntimeStateModule() {
|
||||||
|
function createRuntimeStateHelpers(deps = {}) {
|
||||||
|
const {
|
||||||
|
DEFAULT_ACTIVE_FLOW_ID = 'openai',
|
||||||
|
defaultStepStatuses = {},
|
||||||
|
getStepDefinitionForState = null,
|
||||||
|
} = 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',
|
||||||
|
]),
|
||||||
|
identity: Object.freeze([
|
||||||
|
'resolvedSignupMethod',
|
||||||
|
'accountIdentifierType',
|
||||||
|
'accountIdentifier',
|
||||||
|
'registrationEmailState',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'lastEmailTimestamp',
|
||||||
|
'lastSignupCode',
|
||||||
|
'lastLoginCode',
|
||||||
|
'step8VerificationTargetEmail',
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
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 normalizeStepNumber(value) {
|
||||||
|
const step = Math.floor(Number(value) || 0);
|
||||||
|
return step > 0 ? step : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeNodeStatus(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return 'pending';
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDefaultStepStatuses() {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(normalizePlainObject(defaultStepStatuses)).map(([key, value]) => [
|
||||||
|
String(key),
|
||||||
|
normalizeNodeStatus(value),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeStepStatuses(value) {
|
||||||
|
const base = buildDefaultStepStatuses();
|
||||||
|
if (!isPlainObject(value)) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = { ...base };
|
||||||
|
for (const [key, status] of Object.entries(value)) {
|
||||||
|
const step = normalizeStepNumber(key);
|
||||||
|
if (!step) continue;
|
||||||
|
next[String(step)] = normalizeNodeStatus(status);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLegacyStepCompat(value = {}, state = {}) {
|
||||||
|
const candidate = normalizePlainObject(value);
|
||||||
|
const currentStep = normalizeStepNumber(
|
||||||
|
Object.prototype.hasOwnProperty.call(state, 'currentStep')
|
||||||
|
? state.currentStep
|
||||||
|
: candidate.currentStep
|
||||||
|
);
|
||||||
|
const stepStatuses = normalizeStepStatuses(
|
||||||
|
Object.prototype.hasOwnProperty.call(state, 'stepStatuses')
|
||||||
|
? state.stepStatuses
|
||||||
|
: candidate.stepStatuses
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentStep,
|
||||||
|
stepStatuses,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveStepKey(step, state = {}) {
|
||||||
|
const numericStep = normalizeStepNumber(step);
|
||||||
|
if (!numericStep || typeof getStepDefinitionForState !== 'function') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(getStepDefinitionForState(numericStep, state)?.key || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeNodeStatuses(value, state = {}, legacyStepCompat = null) {
|
||||||
|
if (isPlainObject(value) && Object.keys(value).length > 0) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value)
|
||||||
|
.map(([key, status]) => [String(key || '').trim(), normalizeNodeStatus(status)])
|
||||||
|
.filter(([key]) => Boolean(key))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const compat = legacyStepCompat || normalizeLegacyStepCompat({}, state);
|
||||||
|
const next = {};
|
||||||
|
for (const [stepKey, status] of Object.entries(compat.stepStatuses || {})) {
|
||||||
|
const step = normalizeStepNumber(stepKey);
|
||||||
|
if (!step) continue;
|
||||||
|
const nodeKey = resolveStepKey(step, state) || `step-${step}`;
|
||||||
|
next[nodeKey] = 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 flattenOpenAiFlowState(flowState = {}) {
|
||||||
|
const openaiState = normalizePlainObject(flowState.openai);
|
||||||
|
const next = {};
|
||||||
|
for (const [groupKey, fields] of Object.entries(OPENAI_FLOW_FIELD_GROUPS)) {
|
||||||
|
const group = normalizePlainObject(openaiState[groupKey]);
|
||||||
|
for (const field of fields) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(group, field)) {
|
||||||
|
next[field] = cloneValue(group[field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOpenAiFlowState(baseValue = {}, state = {}) {
|
||||||
|
const baseFlowState = cloneValue(normalizePlainObject(baseValue));
|
||||||
|
const baseOpenAi = cloneValue(normalizePlainObject(baseFlowState.openai));
|
||||||
|
const openaiState = {
|
||||||
|
...baseOpenAi,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [groupKey, fields] of Object.entries(OPENAI_FLOW_FIELD_GROUPS)) {
|
||||||
|
openaiState[groupKey] = {
|
||||||
|
...cloneValue(normalizePlainObject(baseOpenAi[groupKey])),
|
||||||
|
...pickDefinedFields(state, fields),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseFlowState,
|
||||||
|
openai: openaiState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRuntimeStateDefault() {
|
||||||
|
return {
|
||||||
|
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
activeRunId: '',
|
||||||
|
currentNodeId: '',
|
||||||
|
nodeStatuses: {},
|
||||||
|
sharedState: {},
|
||||||
|
serviceState: {
|
||||||
|
proxy: {},
|
||||||
|
},
|
||||||
|
flowState: {
|
||||||
|
openai: {
|
||||||
|
auth: {},
|
||||||
|
platformBinding: {},
|
||||||
|
plus: {},
|
||||||
|
phoneVerification: {},
|
||||||
|
luckmail: {},
|
||||||
|
identity: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legacyStepCompat: {
|
||||||
|
currentStep: 0,
|
||||||
|
stepStatuses: buildDefaultStepStatuses(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureRuntimeState(state = {}) {
|
||||||
|
const baseRuntimeState = {
|
||||||
|
...buildRuntimeStateDefault(),
|
||||||
|
...cloneValue(normalizePlainObject(state.runtimeState)),
|
||||||
|
};
|
||||||
|
const activeFlowId = normalizeFlowId(
|
||||||
|
Object.prototype.hasOwnProperty.call(state, 'activeFlowId')
|
||||||
|
? state.activeFlowId
|
||||||
|
: baseRuntimeState.activeFlowId
|
||||||
|
);
|
||||||
|
const legacyStepCompat = normalizeLegacyStepCompat(baseRuntimeState.legacyStepCompat, state);
|
||||||
|
const currentNodeId = String(
|
||||||
|
Object.prototype.hasOwnProperty.call(state, 'currentNodeId')
|
||||||
|
? state.currentNodeId
|
||||||
|
: (baseRuntimeState.currentNodeId || resolveStepKey(legacyStepCompat.currentStep, state))
|
||||||
|
).trim();
|
||||||
|
const nodeStatuses = normalizeNodeStatuses(
|
||||||
|
Object.prototype.hasOwnProperty.call(state, 'nodeStatuses')
|
||||||
|
? state.nodeStatuses
|
||||||
|
: baseRuntimeState.nodeStatuses,
|
||||||
|
state,
|
||||||
|
legacyStepCompat
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseRuntimeState,
|
||||||
|
activeFlowId,
|
||||||
|
activeRunId: normalizeRunId(
|
||||||
|
Object.prototype.hasOwnProperty.call(state, 'activeRunId')
|
||||||
|
? state.activeRunId
|
||||||
|
: baseRuntimeState.activeRunId
|
||||||
|
),
|
||||||
|
currentNodeId,
|
||||||
|
nodeStatuses,
|
||||||
|
sharedState: buildSharedState(baseRuntimeState.sharedState, state),
|
||||||
|
serviceState: buildServiceState(baseRuntimeState.serviceState, state),
|
||||||
|
flowState: buildOpenAiFlowState(baseRuntimeState.flowState, state),
|
||||||
|
legacyStepCompat,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFlattenedUpdates(updates = {}) {
|
||||||
|
const next = {
|
||||||
|
...updates,
|
||||||
|
};
|
||||||
|
const runtimeState = normalizePlainObject(updates.runtimeState);
|
||||||
|
const legacyStepCompat = normalizePlainObject(updates.legacyStepCompat);
|
||||||
|
const sharedState = normalizePlainObject(updates.sharedState);
|
||||||
|
const serviceState = normalizePlainObject(updates.serviceState);
|
||||||
|
const flowState = normalizePlainObject(updates.flowState);
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'activeFlowId')) {
|
||||||
|
next.activeFlowId = runtimeState.activeFlowId;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'activeRunId')) {
|
||||||
|
next.activeRunId = runtimeState.activeRunId;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'currentNodeId')) {
|
||||||
|
next.currentNodeId = runtimeState.currentNodeId;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'nodeStatuses')) {
|
||||||
|
next.nodeStatuses = cloneValue(runtimeState.nodeStatuses);
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(legacyStepCompat, 'currentStep')) {
|
||||||
|
next.currentStep = legacyStepCompat.currentStep;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(legacyStepCompat, 'stepStatuses')) {
|
||||||
|
next.stepStatuses = cloneValue(legacyStepCompat.stepStatuses);
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'legacyStepCompat')) {
|
||||||
|
const compatCandidate = normalizePlainObject(runtimeState.legacyStepCompat);
|
||||||
|
if (Object.prototype.hasOwnProperty.call(compatCandidate, 'currentStep')) {
|
||||||
|
next.currentStep = compatCandidate.currentStep;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(compatCandidate, 'stepStatuses')) {
|
||||||
|
next.stepStatuses = cloneValue(compatCandidate.stepStatuses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(next, pickDefinedFields(sharedState, RUNTIME_SHARED_FIELDS));
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'sharedState')) {
|
||||||
|
Object.assign(
|
||||||
|
next,
|
||||||
|
pickDefinedFields(normalizePlainObject(runtimeState.sharedState), RUNTIME_SHARED_FIELDS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceProxy = normalizePlainObject(serviceState.proxy);
|
||||||
|
Object.assign(next, pickDefinedFields(serviceProxy, RUNTIME_PROXY_FIELDS));
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'serviceState')) {
|
||||||
|
const runtimeServiceState = normalizePlainObject(runtimeState.serviceState);
|
||||||
|
Object.assign(
|
||||||
|
next,
|
||||||
|
pickDefinedFields(normalizePlainObject(runtimeServiceState.proxy), RUNTIME_PROXY_FIELDS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(next, flattenOpenAiFlowState(flowState));
|
||||||
|
if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowState')) {
|
||||||
|
Object.assign(next, flattenOpenAiFlowState(normalizePlainObject(runtimeState.flowState)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStateView(state = {}) {
|
||||||
|
const runtimeState = ensureRuntimeState(state);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
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),
|
||||||
|
legacyStepCompat: cloneValue(runtimeState.legacyStepCompat),
|
||||||
|
currentStep: runtimeState.legacyStepCompat.currentStep,
|
||||||
|
stepStatuses: cloneValue(runtimeState.legacyStepCompat.stepStatuses),
|
||||||
|
runtimeState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSessionStatePatch(currentState = {}, updates = {}) {
|
||||||
|
const flattenedUpdates = buildFlattenedUpdates(updates);
|
||||||
|
const nextState = {
|
||||||
|
...currentState,
|
||||||
|
...flattenedUpdates,
|
||||||
|
};
|
||||||
|
const runtimeState = ensureRuntimeState(nextState);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...flattenedUpdates,
|
||||||
|
activeFlowId: runtimeState.activeFlowId,
|
||||||
|
activeRunId: runtimeState.activeRunId,
|
||||||
|
currentNodeId: runtimeState.currentNodeId,
|
||||||
|
nodeStatuses: cloneValue(runtimeState.nodeStatuses),
|
||||||
|
currentStep: runtimeState.legacyStepCompat.currentStep,
|
||||||
|
stepStatuses: cloneValue(runtimeState.legacyStepCompat.stepStatuses),
|
||||||
|
runtimeState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
OPENAI_FLOW_FIELD_GROUPS,
|
||||||
|
RUNTIME_PROXY_FIELDS,
|
||||||
|
RUNTIME_SHARED_FIELDS,
|
||||||
|
buildDefaultRuntimeState: buildRuntimeStateDefault,
|
||||||
|
buildSessionStatePatch,
|
||||||
|
buildStateView,
|
||||||
|
ensureRuntimeState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createRuntimeStateHelpers,
|
||||||
|
};
|
||||||
|
});
|
||||||
+123
-35
@@ -11,6 +11,7 @@
|
|||||||
isRetryableContentScriptTransportError,
|
isRetryableContentScriptTransportError,
|
||||||
LOG_PREFIX,
|
LOG_PREFIX,
|
||||||
matchesSourceUrlFamily,
|
matchesSourceUrlFamily,
|
||||||
|
sourceRegistry = null,
|
||||||
setState,
|
setState,
|
||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
STOP_ERROR_MESSAGE,
|
STOP_ERROR_MESSAGE,
|
||||||
@@ -19,6 +20,65 @@
|
|||||||
|
|
||||||
const pendingCommands = new Map();
|
const pendingCommands = new Map();
|
||||||
|
|
||||||
|
function resolveCanonicalSource(source) {
|
||||||
|
if (sourceRegistry?.resolveCanonicalSource) {
|
||||||
|
return sourceRegistry.resolveCanonicalSource(source);
|
||||||
|
}
|
||||||
|
return String(source || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceKeys(source) {
|
||||||
|
if (sourceRegistry?.getSourceKeys) {
|
||||||
|
const registryKeys = sourceRegistry.getSourceKeys(source);
|
||||||
|
if (Array.isArray(registryKeys) && registryKeys.length) {
|
||||||
|
return registryKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const normalized = String(source || '').trim();
|
||||||
|
return normalized ? [normalized] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceCommandKey(source) {
|
||||||
|
const keys = getSourceKeys(source);
|
||||||
|
return keys[0] || String(source || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourcesMatch(leftSource, rightSource) {
|
||||||
|
const left = resolveCanonicalSource(leftSource);
|
||||||
|
const right = resolveCanonicalSource(rightSource);
|
||||||
|
return Boolean(left && right && left === right);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceMapValue(record, source) {
|
||||||
|
const map = record && typeof record === 'object' ? record : {};
|
||||||
|
for (const key of getSourceKeys(source)) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(map, key)) {
|
||||||
|
return map[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSourceMapValue(record, source, value) {
|
||||||
|
const nextRecord = { ...(record || {}) };
|
||||||
|
const keys = getSourceKeys(source);
|
||||||
|
const canonicalKey = keys[0] || String(source || '').trim();
|
||||||
|
for (const key of keys.slice(1)) {
|
||||||
|
delete nextRecord[key];
|
||||||
|
}
|
||||||
|
if (canonicalKey) {
|
||||||
|
nextRecord[canonicalKey] = value;
|
||||||
|
}
|
||||||
|
return nextRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCleanupOwnerSource(cleanupScope) {
|
||||||
|
if (sourceRegistry?.getCleanupOwnerSource) {
|
||||||
|
return sourceRegistry.getCleanupOwnerSource(cleanupScope);
|
||||||
|
}
|
||||||
|
return cleanupScope === 'oauth-localhost-callback' ? 'signup-page' : '';
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAutomationWindowId(value) {
|
function normalizeAutomationWindowId(value) {
|
||||||
if (value === null || value === undefined || value === '') {
|
if (value === null || value === undefined || value === '') {
|
||||||
return null;
|
return null;
|
||||||
@@ -170,7 +230,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function registerTab(source, tabId) {
|
async function registerTab(source, tabId) {
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
let windowId = null;
|
let windowId = null;
|
||||||
if (chrome?.tabs?.get && Number.isInteger(tabId)) {
|
if (chrome?.tabs?.get && Number.isInteger(tabId)) {
|
||||||
const tab = await chrome.tabs.get(tabId).catch(() => null);
|
const tab = await chrome.tabs.get(tabId).catch(() => null);
|
||||||
@@ -180,42 +240,42 @@
|
|||||||
}
|
}
|
||||||
windowId = normalizeAutomationWindowId(tab?.windowId);
|
windowId = normalizeAutomationWindowId(tab?.windowId);
|
||||||
}
|
}
|
||||||
registry[source] = {
|
registry = setSourceMapValue(registry, source, {
|
||||||
tabId,
|
tabId,
|
||||||
ready: true,
|
ready: true,
|
||||||
...(windowId !== null ? { windowId } : {}),
|
...(windowId !== null ? { windowId } : {}),
|
||||||
};
|
});
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
console.log(LOG_PREFIX, `Tab registered: ${source} -> ${tabId}`);
|
console.log(LOG_PREFIX, `Tab registered: ${source} -> ${tabId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isTabAlive(source) {
|
async function isTabAlive(source) {
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
const entry = registry[source];
|
const entry = getSourceMapValue(registry, source);
|
||||||
if (!entry) return false;
|
if (!entry) return false;
|
||||||
try {
|
try {
|
||||||
const tab = await chrome.tabs.get(entry.tabId);
|
const tab = await chrome.tabs.get(entry.tabId);
|
||||||
if (!(await isTabInAutomationWindow(tab))) {
|
if (!(await isTabInAutomationWindow(tab))) {
|
||||||
registry[source] = null;
|
registry = setSourceMapValue(registry, source, null);
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
registry[source] = null;
|
registry = setSourceMapValue(registry, source, null);
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTabId(source) {
|
async function getTabId(source) {
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
const tabId = registry[source]?.tabId || null;
|
const tabId = getSourceMapValue(registry, source)?.tabId || null;
|
||||||
if (!Number.isInteger(tabId)) {
|
if (!Number.isInteger(tabId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!(await isTabInAutomationWindow(tabId))) {
|
if (!(await isTabInAutomationWindow(tabId))) {
|
||||||
registry[source] = null;
|
registry = setSourceMapValue(registry, source, null);
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -225,8 +285,7 @@
|
|||||||
async function rememberSourceLastUrl(source, url) {
|
async function rememberSourceLastUrl(source, url) {
|
||||||
if (!source || !url) return;
|
if (!source || !url) return;
|
||||||
const state = await getState();
|
const state = await getState();
|
||||||
const sourceLastUrls = { ...(state.sourceLastUrls || {}) };
|
const sourceLastUrls = setSourceMapValue(state.sourceLastUrls, source, url);
|
||||||
sourceLastUrls[source] = url;
|
|
||||||
await setState({ sourceLastUrls });
|
await setState({ sourceLastUrls });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +293,7 @@
|
|||||||
const { excludeTabIds = [] } = options;
|
const { excludeTabIds = [] } = options;
|
||||||
const excluded = new Set(excludeTabIds.filter((id) => Number.isInteger(id)));
|
const excluded = new Set(excludeTabIds.filter((id) => Number.isInteger(id)));
|
||||||
const state = await getState();
|
const state = await getState();
|
||||||
const lastUrl = state.sourceLastUrls?.[source];
|
const lastUrl = getSourceMapValue(state.sourceLastUrls, source);
|
||||||
const referenceUrls = [currentUrl, lastUrl].filter(Boolean);
|
const referenceUrls = [currentUrl, lastUrl].filter(Boolean);
|
||||||
|
|
||||||
if (!referenceUrls.length) return;
|
if (!referenceUrls.length) return;
|
||||||
@@ -249,9 +308,10 @@
|
|||||||
|
|
||||||
await chrome.tabs.remove(matchedIds).catch(() => { });
|
await chrome.tabs.remove(matchedIds).catch(() => { });
|
||||||
|
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
if (registry[source]?.tabId && matchedIds.includes(registry[source].tabId)) {
|
const sourceEntry = getSourceMapValue(registry, source);
|
||||||
registry[source] = null;
|
if (sourceEntry?.tabId && matchedIds.includes(sourceEntry.tabId)) {
|
||||||
|
registry = setSourceMapValue(registry, source, null);
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +334,7 @@
|
|||||||
async function closeLocalhostCallbackTabs(callbackUrl, options = {}) {
|
async function closeLocalhostCallbackTabs(callbackUrl, options = {}) {
|
||||||
if (!isLocalhostOAuthCallbackUrl(callbackUrl)) return 0;
|
if (!isLocalhostOAuthCallbackUrl(callbackUrl)) return 0;
|
||||||
|
|
||||||
const { excludeTabIds = [] } = options;
|
const { excludeTabIds = [], ownerSource = getCleanupOwnerSource('oauth-localhost-callback') } = options;
|
||||||
const excluded = new Set(excludeTabIds.filter((id) => Number.isInteger(id)));
|
const excluded = new Set(excludeTabIds.filter((id) => Number.isInteger(id)));
|
||||||
const tabs = await queryTabsInAutomationWindow({});
|
const tabs = await queryTabsInAutomationWindow({});
|
||||||
const matchedIds = tabs
|
const matchedIds = tabs
|
||||||
@@ -286,9 +346,10 @@
|
|||||||
|
|
||||||
await chrome.tabs.remove(matchedIds).catch(() => { });
|
await chrome.tabs.remove(matchedIds).catch(() => { });
|
||||||
|
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
if (registry['signup-page']?.tabId && matchedIds.includes(registry['signup-page'].tabId)) {
|
const ownerEntry = getSourceMapValue(registry, ownerSource);
|
||||||
registry['signup-page'] = null;
|
if (ownerEntry?.tabId && matchedIds.includes(ownerEntry.tabId)) {
|
||||||
|
registry = setSourceMapValue(registry, ownerSource, null);
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +529,7 @@
|
|||||||
while (Date.now() - start < timeoutMs) {
|
while (Date.now() - start < timeoutMs) {
|
||||||
attempt += 1;
|
attempt += 1;
|
||||||
const pong = await pingContentScriptOnTab(tabId);
|
const pong = await pingContentScriptOnTab(tabId);
|
||||||
if (pong?.ok && (!pong.source || pong.source === source)) {
|
if (pong?.ok && (!pong.source || sourcesMatch(pong.source, source))) {
|
||||||
console.log(LOG_PREFIX, `[ensureContentScriptReadyOnTab] ready ${source} tab=${tabId} on attempt ${attempt} after ${Date.now() - start}ms`);
|
console.log(LOG_PREFIX, `[ensureContentScriptReadyOnTab] ready ${source} tab=${tabId} on attempt ${attempt} after ${Date.now() - start}ms`);
|
||||||
await registerTab(source, tabId);
|
await registerTab(source, tabId);
|
||||||
return;
|
return;
|
||||||
@@ -478,9 +539,13 @@
|
|||||||
throw new Error(`${getSourceLabel(source)} 内容脚本未就绪,且未提供可用的注入文件。`);
|
throw new Error(`${getSourceLabel(source)} 内容脚本未就绪,且未提供可用的注入文件。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
if (registry[source]) {
|
const sourceEntry = getSourceMapValue(registry, source);
|
||||||
registry[source].ready = false;
|
if (sourceEntry) {
|
||||||
|
registry = setSourceMapValue(registry, source, {
|
||||||
|
...sourceEntry,
|
||||||
|
ready: false,
|
||||||
|
});
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +570,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pongAfterInject = await pingContentScriptOnTab(tabId);
|
const pongAfterInject = await pingContentScriptOnTab(tabId);
|
||||||
if (pongAfterInject?.ok && (!pongAfterInject.source || pongAfterInject.source === source)) {
|
if (pongAfterInject?.ok && (!pongAfterInject.source || sourcesMatch(pongAfterInject.source, source))) {
|
||||||
console.log(LOG_PREFIX, `[ensureContentScriptReadyOnTab] ready after inject ${source} tab=${tabId} on attempt ${attempt} after ${Date.now() - start}ms`);
|
console.log(LOG_PREFIX, `[ensureContentScriptReadyOnTab] ready after inject ${source} tab=${tabId} on attempt ${attempt} after ${Date.now() - start}ms`);
|
||||||
await registerTab(source, tabId);
|
await registerTab(source, tabId);
|
||||||
return;
|
return;
|
||||||
@@ -609,14 +674,16 @@
|
|||||||
|
|
||||||
function queueCommand(source, message, timeout = 15000) {
|
function queueCommand(source, message, timeout = 15000) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const commandKey = getSourceCommandKey(source);
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
pendingCommands.delete(source);
|
pendingCommands.delete(commandKey);
|
||||||
reject(new Error(`Content script on ${source} did not respond in ${timeout / 1000}s. Try refreshing the tab and retry.`));
|
reject(new Error(`Content script on ${source} did not respond in ${timeout / 1000}s. Try refreshing the tab and retry.`));
|
||||||
}, timeout);
|
}, timeout);
|
||||||
pendingCommands.set(source, {
|
pendingCommands.set(commandKey, {
|
||||||
message,
|
message,
|
||||||
resolve,
|
resolve,
|
||||||
reject,
|
reject,
|
||||||
|
source,
|
||||||
timer,
|
timer,
|
||||||
responseTimeoutMs: timeout,
|
responseTimeoutMs: timeout,
|
||||||
});
|
});
|
||||||
@@ -625,11 +692,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function flushCommand(source, tabId) {
|
function flushCommand(source, tabId) {
|
||||||
const pending = pendingCommands.get(source);
|
const pending = pendingCommands.get(getSourceCommandKey(source));
|
||||||
if (pending) {
|
if (pending) {
|
||||||
clearTimeout(pending.timer);
|
clearTimeout(pending.timer);
|
||||||
pendingCommands.delete(source);
|
pendingCommands.delete(getSourceCommandKey(source));
|
||||||
sendTabMessageWithTimeout(tabId, source, pending.message, pending.responseTimeoutMs).then(pending.resolve).catch(pending.reject);
|
sendTabMessageWithTimeout(
|
||||||
|
tabId,
|
||||||
|
pending.source || source,
|
||||||
|
pending.message,
|
||||||
|
pending.responseTimeoutMs
|
||||||
|
).then(pending.resolve).catch(pending.reject);
|
||||||
console.log(LOG_PREFIX, `Flushed queued command to ${source} (tab ${tabId})`);
|
console.log(LOG_PREFIX, `Flushed queued command to ${source} (tab ${tabId})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,18 +749,29 @@
|
|||||||
const sameUrl = currentTab.url === url;
|
const sameUrl = currentTab.url === url;
|
||||||
const shouldReloadOnReuse = sameUrl && options.reloadIfSameUrl;
|
const shouldReloadOnReuse = sameUrl && options.reloadIfSameUrl;
|
||||||
|
|
||||||
const registry = await getTabRegistry();
|
let registry = await getTabRegistry();
|
||||||
|
const sourceEntry = getSourceMapValue(registry, source);
|
||||||
if (sameUrl) {
|
if (sameUrl) {
|
||||||
await chrome.tabs.update(tabId, { active: true });
|
await chrome.tabs.update(tabId, { active: true });
|
||||||
if (shouldReloadOnReuse) {
|
if (shouldReloadOnReuse) {
|
||||||
if (registry[source]) registry[source].ready = false;
|
if (sourceEntry) {
|
||||||
|
registry = setSourceMapValue(registry, source, {
|
||||||
|
...sourceEntry,
|
||||||
|
ready: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
await chrome.tabs.reload(tabId);
|
await chrome.tabs.reload(tabId);
|
||||||
await waitForTabUpdateComplete(tabId);
|
await waitForTabUpdateComplete(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.inject) {
|
if (options.inject) {
|
||||||
if (registry[source]) registry[source].ready = false;
|
if (sourceEntry) {
|
||||||
|
registry = setSourceMapValue(registry, source, {
|
||||||
|
...sourceEntry,
|
||||||
|
ready: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
if (options.injectSource) {
|
if (options.injectSource) {
|
||||||
await chrome.scripting.executeScript({
|
await chrome.scripting.executeScript({
|
||||||
@@ -710,7 +793,12 @@
|
|||||||
return tabId;
|
return tabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registry[source]) registry[source].ready = false;
|
if (sourceEntry) {
|
||||||
|
registry = setSourceMapValue(registry, source, {
|
||||||
|
...sourceEntry,
|
||||||
|
ready: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
await setState({ tabRegistry: registry });
|
await setState({ tabRegistry: registry });
|
||||||
await chrome.tabs.update(tabId, { url, active: true });
|
await chrome.tabs.update(tabId, { url, active: true });
|
||||||
|
|
||||||
@@ -765,7 +853,7 @@
|
|||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
const { responseTimeoutMs = getContentScriptResponseTimeoutMs(message) } = options;
|
const { responseTimeoutMs = getContentScriptResponseTimeoutMs(message) } = options;
|
||||||
const registry = await getTabRegistry();
|
const registry = await getTabRegistry();
|
||||||
const entry = registry[source];
|
const entry = getSourceMapValue(registry, source);
|
||||||
|
|
||||||
if (!entry || !entry.ready) {
|
if (!entry || !entry.ready) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
function createVerificationFlowHelpers(deps = {}) {
|
function createVerificationFlowHelpers(deps = {}) {
|
||||||
const {
|
const {
|
||||||
addLog: rawAddLog = async () => {},
|
addLog: rawAddLog = async () => {},
|
||||||
|
buildVerificationPollPayload: externalBuildVerificationPollPayload = null,
|
||||||
chrome,
|
chrome,
|
||||||
closeConflictingTabsForSource,
|
closeConflictingTabsForSource,
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
@@ -408,6 +409,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getVerificationPollPayload(step, state, overrides = {}) {
|
function getVerificationPollPayload(step, state, overrides = {}) {
|
||||||
|
if (typeof externalBuildVerificationPollPayload === 'function') {
|
||||||
|
return externalBuildVerificationPollPayload(step, state, overrides);
|
||||||
|
}
|
||||||
const is2925Provider = state?.mailProvider === '2925';
|
const is2925Provider = state?.mailProvider === '2925';
|
||||||
const mail2925MatchTargetEmail = is2925Provider
|
const mail2925MatchTargetEmail = is2925Provider
|
||||||
&& String(state?.mail2925Mode || '').trim().toLowerCase() === 'receive';
|
&& String(state?.mail2925Mode || '').trim().toLowerCase() === 'receive';
|
||||||
|
|||||||
+19
-3
@@ -7,8 +7,16 @@ function detectScriptSource({
|
|||||||
url = '',
|
url = '',
|
||||||
hostname = '',
|
hostname = '',
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
const sourceRegistry = globalThis?.MultiPageSourceRegistry?.createSourceRegistry?.();
|
||||||
|
if (sourceRegistry?.detectSourceFromLocation) {
|
||||||
|
return sourceRegistry.detectSourceFromLocation({
|
||||||
|
injectedSource,
|
||||||
|
url,
|
||||||
|
hostname,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (injectedSource) return injectedSource;
|
if (injectedSource) return injectedSource;
|
||||||
if (url.includes('auth0.openai.com') || url.includes('auth.openai.com') || url.includes('accounts.openai.com')) return 'signup-page';
|
if (url.includes('auth0.openai.com') || url.includes('auth.openai.com') || url.includes('accounts.openai.com')) return 'openai-auth';
|
||||||
if (hostname === 'mail.qq.com' || hostname === 'wx.mail.qq.com') return 'qq-mail';
|
if (hostname === 'mail.qq.com' || hostname === 'wx.mail.qq.com') return 'qq-mail';
|
||||||
if (
|
if (
|
||||||
hostname === 'mail.163.com'
|
hostname === 'mail.163.com'
|
||||||
@@ -22,8 +30,7 @@ function detectScriptSource({
|
|||||||
if (url.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
|
if (url.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
|
||||||
if (url.includes('chatgpt.com')) return 'chatgpt';
|
if (url.includes('chatgpt.com')) return 'chatgpt';
|
||||||
if (url.includes("2925.com")) return "mail-2925";
|
if (url.includes("2925.com")) return "mail-2925";
|
||||||
// VPS panel — detected dynamically since URL is configurable
|
return 'unknown-source';
|
||||||
return 'vps-panel';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCRIPT_SOURCE = (() => {
|
const SCRIPT_SOURCE = (() => {
|
||||||
@@ -294,6 +301,10 @@ function log(message, level = 'info', options = {}) {
|
|||||||
* Report that this content script is loaded and ready.
|
* Report that this content script is loaded and ready.
|
||||||
*/
|
*/
|
||||||
function reportReady() {
|
function reportReady() {
|
||||||
|
if (getRuntimeScriptSource() === 'unknown-source') {
|
||||||
|
console.warn(LOG_PREFIX, 'skip CONTENT_SCRIPT_READY for unknown source');
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log(LOG_PREFIX, '内容脚本已就绪');
|
console.log(LOG_PREFIX, '内容脚本已就绪');
|
||||||
const message = {
|
const message = {
|
||||||
type: 'CONTENT_SCRIPT_READY',
|
type: 'CONTENT_SCRIPT_READY',
|
||||||
@@ -439,6 +450,10 @@ async function humanPause(min = 250, max = 850) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shouldReportReadyForFrame(source, isChildFrame) {
|
function shouldReportReadyForFrame(source, isChildFrame) {
|
||||||
|
const sourceRegistry = globalThis?.MultiPageSourceRegistry?.createSourceRegistry?.();
|
||||||
|
if (sourceRegistry?.shouldReportReadyForFrame) {
|
||||||
|
return sourceRegistry.shouldReportReadyForFrame(source, isChildFrame);
|
||||||
|
}
|
||||||
if (!isChildFrame) return true;
|
if (!isChildFrame) return true;
|
||||||
return ![
|
return ![
|
||||||
'qq-mail',
|
'qq-mail',
|
||||||
@@ -447,6 +462,7 @@ function shouldReportReadyForFrame(source, isChildFrame) {
|
|||||||
'mail-2925',
|
'mail-2925',
|
||||||
'inbucket-mail',
|
'inbucket-mail',
|
||||||
'plus-checkout',
|
'plus-checkout',
|
||||||
|
'unknown-source',
|
||||||
].includes(source);
|
].includes(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
(function attachOpenAiMailRules(root, factory) {
|
||||||
|
root.MultiPageOpenAiMailRules = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createOpenAiMailRulesModule() {
|
||||||
|
const SIGNUP_CODE_RULE_ID = 'openai-signup-code';
|
||||||
|
const LOGIN_CODE_RULE_ID = 'openai-login-code';
|
||||||
|
|
||||||
|
function createOpenAiMailRules(deps = {}) {
|
||||||
|
const {
|
||||||
|
getHotmailVerificationRequestTimestamp = () => 0,
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS = 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS = 15,
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function isMail2925Provider(state = {}) {
|
||||||
|
return String(state?.mailProvider || '').trim().toLowerCase() === '2925';
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldMatchMail2925TargetEmail(state = {}) {
|
||||||
|
return isMail2925Provider(state)
|
||||||
|
&& String(state?.mail2925Mode || '').trim().toLowerCase() === 'receive';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuleDefinition(step, state = {}) {
|
||||||
|
const normalizedStep = Number(step) === 4 ? 4 : 8;
|
||||||
|
const mail2925Provider = isMail2925Provider(state);
|
||||||
|
const signupStep = normalizedStep === 4;
|
||||||
|
|
||||||
|
return {
|
||||||
|
flowId: 'openai',
|
||||||
|
ruleId: signupStep ? SIGNUP_CODE_RULE_ID : LOGIN_CODE_RULE_ID,
|
||||||
|
step: normalizedStep,
|
||||||
|
artifactType: 'code',
|
||||||
|
filterAfterTimestamp: mail2925Provider
|
||||||
|
? 0
|
||||||
|
: getHotmailVerificationRequestTimestamp(normalizedStep, state),
|
||||||
|
senderFilters: signupStep
|
||||||
|
? ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward']
|
||||||
|
: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||||
|
subjectFilters: signupStep
|
||||||
|
? ['verify', 'verification', 'code', '验证码', 'confirm']
|
||||||
|
: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||||
|
targetEmail: signupStep
|
||||||
|
? state?.email
|
||||||
|
: (String(state?.step8VerificationTargetEmail || '').trim() || state?.email),
|
||||||
|
mail2925MatchTargetEmail: shouldMatchMail2925TargetEmail(state),
|
||||||
|
maxAttempts: mail2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
|
||||||
|
intervalMs: mail2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVerificationPollPayload(step, state = {}, overrides = {}) {
|
||||||
|
return {
|
||||||
|
...getRuleDefinition(step, state),
|
||||||
|
...(overrides || {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildVerificationPollPayload,
|
||||||
|
getRuleDefinition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
LOGIN_CODE_RULE_ID,
|
||||||
|
SIGNUP_CODE_RULE_ID,
|
||||||
|
createOpenAiMailRules,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"content/activation-utils.js",
|
"content/activation-utils.js",
|
||||||
|
"shared/source-registry.js",
|
||||||
"content/utils.js",
|
"content/utils.js",
|
||||||
"content/operation-delay.js",
|
"content/operation-delay.js",
|
||||||
"content/auth-page-recovery.js",
|
"content/auth-page-recovery.js",
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"content/activation-utils.js",
|
"content/activation-utils.js",
|
||||||
|
"shared/source-registry.js",
|
||||||
"content/utils.js",
|
"content/utils.js",
|
||||||
"content/qq-mail.js"
|
"content/qq-mail.js"
|
||||||
],
|
],
|
||||||
@@ -81,6 +83,7 @@
|
|||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"content/activation-utils.js",
|
"content/activation-utils.js",
|
||||||
|
"shared/source-registry.js",
|
||||||
"content/utils.js",
|
"content/utils.js",
|
||||||
"content/mail-163.js"
|
"content/mail-163.js"
|
||||||
],
|
],
|
||||||
@@ -94,6 +97,7 @@
|
|||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"content/activation-utils.js",
|
"content/activation-utils.js",
|
||||||
|
"shared/source-registry.js",
|
||||||
"content/utils.js",
|
"content/utils.js",
|
||||||
"content/icloud-mail.js"
|
"content/icloud-mail.js"
|
||||||
],
|
],
|
||||||
@@ -106,6 +110,7 @@
|
|||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"content/activation-utils.js",
|
"content/activation-utils.js",
|
||||||
|
"shared/source-registry.js",
|
||||||
"content/utils.js",
|
"content/utils.js",
|
||||||
"content/operation-delay.js",
|
"content/operation-delay.js",
|
||||||
"content/duck-mail.js"
|
"content/duck-mail.js"
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
(function attachMultiPageFlowCapabilities(root, factory) {
|
||||||
|
root.MultiPageFlowCapabilities = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
|
||||||
|
const DEFAULT_FLOW_ID = 'openai';
|
||||||
|
const DEFAULT_PANEL_MODE = 'cpa';
|
||||||
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
|
const VALID_PANEL_MODES = Object.freeze(['cpa', 'sub2api', 'codex2api']);
|
||||||
|
|
||||||
|
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
|
||||||
|
supportsEmailSignup: true,
|
||||||
|
supportsPhoneSignup: false,
|
||||||
|
supportsPhoneVerificationSettings: false,
|
||||||
|
supportsPlusMode: false,
|
||||||
|
supportsContributionMode: false,
|
||||||
|
supportsPlatformBinding: [],
|
||||||
|
supportsLuckmail: false,
|
||||||
|
supportsOauthTimeoutBudget: false,
|
||||||
|
canSwitchFlow: false,
|
||||||
|
stepDefinitionMode: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
const FLOW_CAPABILITIES = Object.freeze({
|
||||||
|
openai: Object.freeze({
|
||||||
|
...DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
supportsPhoneSignup: true,
|
||||||
|
supportsPhoneVerificationSettings: true,
|
||||||
|
supportsPlusMode: true,
|
||||||
|
supportsContributionMode: true,
|
||||||
|
supportsPlatformBinding: ['cpa', 'sub2api', 'codex2api'],
|
||||||
|
supportsLuckmail: true,
|
||||||
|
supportsOauthTimeoutBudget: true,
|
||||||
|
stepDefinitionMode: 'openai-dynamic',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_PANEL_CAPABILITIES = Object.freeze({
|
||||||
|
supportsPhoneSignup: true,
|
||||||
|
requiresPhoneSignupWarning: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const PANEL_CAPABILITIES = Object.freeze({
|
||||||
|
cpa: Object.freeze({
|
||||||
|
supportsPhoneSignup: true,
|
||||||
|
requiresPhoneSignupWarning: true,
|
||||||
|
}),
|
||||||
|
sub2api: Object.freeze({
|
||||||
|
supportsPhoneSignup: true,
|
||||||
|
requiresPhoneSignupWarning: false,
|
||||||
|
}),
|
||||||
|
codex2api: Object.freeze({
|
||||||
|
supportsPhoneSignup: true,
|
||||||
|
requiresPhoneSignupWarning: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (normalized) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||||
|
return fallbackValue || DEFAULT_FLOW_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePanelMode(value = '', fallback = DEFAULT_PANEL_MODE) {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
if (VALID_PANEL_MODES.includes(normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||||
|
return VALID_PANEL_MODES.includes(fallbackValue) ? fallbackValue : DEFAULT_PANEL_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSignupMethod(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE
|
||||||
|
? SIGNUP_METHOD_PHONE
|
||||||
|
: SIGNUP_METHOD_EMAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePanelModeList(values = []) {
|
||||||
|
if (!Array.isArray(values)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const seen = new Set();
|
||||||
|
const normalized = [];
|
||||||
|
values.forEach((value) => {
|
||||||
|
const mode = normalizePanelMode(value, '');
|
||||||
|
if (!mode || seen.has(mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.add(mode);
|
||||||
|
normalized.push(mode);
|
||||||
|
});
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFlowCapabilityRegistry(deps = {}) {
|
||||||
|
const {
|
||||||
|
defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
defaultFlowId = DEFAULT_FLOW_ID,
|
||||||
|
defaultPanelCapabilities = DEFAULT_PANEL_CAPABILITIES,
|
||||||
|
flowCapabilities = FLOW_CAPABILITIES,
|
||||||
|
panelCapabilities = PANEL_CAPABILITIES,
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function getFlowCapabilities(flowId) {
|
||||||
|
const normalizedFlowId = normalizeFlowId(flowId, defaultFlowId);
|
||||||
|
const entry = flowCapabilities[normalizedFlowId] || null;
|
||||||
|
return {
|
||||||
|
...defaultFlowCapabilities,
|
||||||
|
...(entry || {}),
|
||||||
|
supportsPlatformBinding: normalizePanelModeList(entry?.supportsPlatformBinding || defaultFlowCapabilities.supportsPlatformBinding),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanelCapabilities(panelMode) {
|
||||||
|
const normalizedPanelMode = normalizePanelMode(panelMode);
|
||||||
|
return {
|
||||||
|
...defaultPanelCapabilities,
|
||||||
|
...(panelCapabilities[normalizedPanelMode] || {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSidepanelCapabilities(options = {}) {
|
||||||
|
const state = options?.state || {};
|
||||||
|
const activeFlowId = normalizeFlowId(
|
||||||
|
options?.activeFlowId ?? state?.activeFlowId,
|
||||||
|
defaultFlowId
|
||||||
|
);
|
||||||
|
const flowState = getFlowCapabilities(activeFlowId);
|
||||||
|
const requestedPanelMode = normalizePanelMode(
|
||||||
|
options?.panelMode ?? state?.panelMode,
|
||||||
|
DEFAULT_PANEL_MODE
|
||||||
|
);
|
||||||
|
const supportedPanelModes = normalizePanelModeList(flowState.supportsPlatformBinding);
|
||||||
|
const panelModeSupported = supportedPanelModes.length === 0
|
||||||
|
? true
|
||||||
|
: supportedPanelModes.includes(requestedPanelMode);
|
||||||
|
const effectivePanelMode = panelModeSupported
|
||||||
|
? requestedPanelMode
|
||||||
|
: supportedPanelModes[0];
|
||||||
|
const panelState = getPanelCapabilities(effectivePanelMode);
|
||||||
|
const runtimeLocks = {
|
||||||
|
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
|
||||||
|
contributionMode: flowState.supportsContributionMode && Boolean(state?.contributionMode),
|
||||||
|
phoneVerificationEnabled: flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled),
|
||||||
|
plusModeEnabled: flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
|
||||||
|
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
|
||||||
|
};
|
||||||
|
const effectiveSignupMethods = [];
|
||||||
|
if (flowState.supportsEmailSignup !== false) {
|
||||||
|
effectiveSignupMethods.push(SIGNUP_METHOD_EMAIL);
|
||||||
|
}
|
||||||
|
const canSelectPhoneSignup = Boolean(flowState.supportsPhoneSignup)
|
||||||
|
&& Boolean(panelState.supportsPhoneSignup)
|
||||||
|
&& runtimeLocks.phoneVerificationEnabled
|
||||||
|
&& !runtimeLocks.plusModeEnabled
|
||||||
|
&& !runtimeLocks.contributionMode;
|
||||||
|
if (canSelectPhoneSignup) {
|
||||||
|
effectiveSignupMethods.push(SIGNUP_METHOD_PHONE);
|
||||||
|
}
|
||||||
|
if (!effectiveSignupMethods.length) {
|
||||||
|
effectiveSignupMethods.push(SIGNUP_METHOD_EMAIL);
|
||||||
|
}
|
||||||
|
const requestedSignupMethod = normalizeSignupMethod(
|
||||||
|
options?.signupMethod ?? state?.signupMethod
|
||||||
|
);
|
||||||
|
const effectiveSignupMethod = requestedSignupMethod === SIGNUP_METHOD_PHONE && canSelectPhoneSignup
|
||||||
|
? SIGNUP_METHOD_PHONE
|
||||||
|
: (effectiveSignupMethods.includes(SIGNUP_METHOD_EMAIL)
|
||||||
|
? SIGNUP_METHOD_EMAIL
|
||||||
|
: effectiveSignupMethods[0]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeFlowId,
|
||||||
|
canShowContributionMode: Boolean(flowState.supportsContributionMode),
|
||||||
|
canShowLuckmail: Boolean(flowState.supportsLuckmail),
|
||||||
|
canShowPhoneSettings: Boolean(flowState.supportsPhoneVerificationSettings),
|
||||||
|
canShowPlusSettings: Boolean(flowState.supportsPlusMode),
|
||||||
|
canSwitchFlow: Boolean(flowState.canSwitchFlow),
|
||||||
|
canUsePhoneSignup: canSelectPhoneSignup,
|
||||||
|
canUseSelectedPanelMode: panelModeSupported,
|
||||||
|
effectivePanelMode,
|
||||||
|
effectiveSignupMethod,
|
||||||
|
effectiveSignupMethods,
|
||||||
|
flowCapabilities: flowState,
|
||||||
|
panelCapabilities: panelState,
|
||||||
|
panelMode: effectivePanelMode,
|
||||||
|
requestedPanelMode,
|
||||||
|
requestedSignupMethod,
|
||||||
|
runtimeLocks,
|
||||||
|
shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE
|
||||||
|
&& Boolean(panelState.requiresPhoneSignupWarning),
|
||||||
|
stepDefinitionOptions: {
|
||||||
|
activeFlowId,
|
||||||
|
panelMode: effectivePanelMode,
|
||||||
|
plusModeEnabled: runtimeLocks.plusModeEnabled,
|
||||||
|
signupMethod: effectiveSignupMethod,
|
||||||
|
},
|
||||||
|
supportedPanelModes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function canUsePhoneSignup(state = {}) {
|
||||||
|
return resolveSidepanelCapabilities({ state }).canUsePhoneSignup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSignupMethod(state = {}, signupMethod = undefined) {
|
||||||
|
return resolveSidepanelCapabilities({
|
||||||
|
signupMethod,
|
||||||
|
state,
|
||||||
|
}).effectiveSignupMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canUsePhoneSignup,
|
||||||
|
getFlowCapabilities,
|
||||||
|
getPanelCapabilities,
|
||||||
|
normalizeFlowId,
|
||||||
|
normalizePanelMode,
|
||||||
|
normalizeSignupMethod,
|
||||||
|
resolveSidepanelCapabilities,
|
||||||
|
resolveSignupMethod,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createFlowCapabilityRegistry,
|
||||||
|
DEFAULT_FLOW_CAPABILITIES,
|
||||||
|
DEFAULT_FLOW_ID,
|
||||||
|
DEFAULT_PANEL_CAPABILITIES,
|
||||||
|
DEFAULT_PANEL_MODE,
|
||||||
|
FLOW_CAPABILITIES,
|
||||||
|
PANEL_CAPABILITIES,
|
||||||
|
SIGNUP_METHOD_EMAIL,
|
||||||
|
SIGNUP_METHOD_PHONE,
|
||||||
|
normalizeFlowId,
|
||||||
|
normalizePanelMode,
|
||||||
|
normalizeSignupMethod,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
(function attachMultiPageSourceRegistry(root, factory) {
|
||||||
|
root.MultiPageSourceRegistry = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createSourceRegistryModule() {
|
||||||
|
const SOURCE_ALIASES = Object.freeze({
|
||||||
|
'signup-page': 'openai-auth',
|
||||||
|
});
|
||||||
|
|
||||||
|
const SOURCE_DEFINITIONS = Object.freeze({
|
||||||
|
'openai-auth': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: '认证页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'openai-auth-family',
|
||||||
|
driverId: 'content/signup-page',
|
||||||
|
cleanupScopes: ['oauth-localhost-callback'],
|
||||||
|
},
|
||||||
|
chatgpt: {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-entry',
|
||||||
|
label: 'ChatGPT 首页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'chatgpt-entry-family',
|
||||||
|
driverId: null,
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'qq-mail': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: 'QQ 邮箱',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'qq-mail-family',
|
||||||
|
driverId: 'content/qq-mail',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'mail-163': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: '163 邮箱',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'mail-163-family',
|
||||||
|
driverId: 'content/mail-163',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'gmail-mail': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: 'Gmail 邮箱',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'gmail-mail-family',
|
||||||
|
driverId: 'content/gmail-mail',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'icloud-mail': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: 'iCloud 邮箱',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'icloud-mail-family',
|
||||||
|
driverId: 'content/icloud-mail',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'inbucket-mail': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: 'Inbucket 邮箱',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'inbucket-mail-family',
|
||||||
|
driverId: 'content/inbucket-mail',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'mail-2925': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: '2925 邮箱',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'mail-2925-family',
|
||||||
|
driverId: 'content/mail-2925',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'duck-mail': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'mail-provider',
|
||||||
|
label: 'Duck 邮箱',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'duck-mail-family',
|
||||||
|
driverId: 'content/duck-mail',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'vps-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'panel-page',
|
||||||
|
label: 'CPA 面板',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'vps-panel-family',
|
||||||
|
driverId: 'content/vps-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'sub2api-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'panel-page',
|
||||||
|
label: 'SUB2API 后台',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'sub2api-panel-family',
|
||||||
|
driverId: 'content/sub2api-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'codex2api-panel': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'panel-page',
|
||||||
|
label: 'Codex2API 后台',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'codex2api-panel-family',
|
||||||
|
driverId: 'content/sub2api-panel',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'plus-checkout': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'Plus Checkout',
|
||||||
|
readyPolicy: 'top-frame-only',
|
||||||
|
family: 'plus-checkout-family',
|
||||||
|
driverId: 'content/plus-checkout',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'paypal-flow': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'PayPal 授权页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'paypal-flow-family',
|
||||||
|
driverId: 'content/paypal-flow',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'gopay-flow': {
|
||||||
|
flowId: 'openai',
|
||||||
|
kind: 'flow-page',
|
||||||
|
label: 'GoPay 授权页',
|
||||||
|
readyPolicy: 'allow-child-frame',
|
||||||
|
family: 'gopay-flow-family',
|
||||||
|
driverId: 'content/gopay-flow',
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
'unknown-source': {
|
||||||
|
flowId: null,
|
||||||
|
kind: 'unknown',
|
||||||
|
label: '未知来源',
|
||||||
|
readyPolicy: 'disabled',
|
||||||
|
family: 'unknown-family',
|
||||||
|
driverId: null,
|
||||||
|
cleanupScopes: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const DRIVER_DEFINITIONS = Object.freeze({
|
||||||
|
'content/signup-page': {
|
||||||
|
sourceId: 'openai-auth',
|
||||||
|
commands: [
|
||||||
|
'OPEN_SIGNUP',
|
||||||
|
'SUBMIT_SIGNUP_IDENTIFIER',
|
||||||
|
'SUBMIT_PASSWORD',
|
||||||
|
'SUBMIT_PROFILE',
|
||||||
|
'SUBMIT_LOGIN_CODE',
|
||||||
|
'SUBMIT_PHONE_CODE',
|
||||||
|
'DETECT_AUTH_STATE',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'content/qq-mail': {
|
||||||
|
sourceId: 'qq-mail',
|
||||||
|
commands: ['POLL_EMAIL'],
|
||||||
|
},
|
||||||
|
'content/mail-163': {
|
||||||
|
sourceId: 'mail-163',
|
||||||
|
commands: ['POLL_EMAIL'],
|
||||||
|
},
|
||||||
|
'content/gmail-mail': {
|
||||||
|
sourceId: 'gmail-mail',
|
||||||
|
commands: ['POLL_EMAIL'],
|
||||||
|
},
|
||||||
|
'content/icloud-mail': {
|
||||||
|
sourceId: 'icloud-mail',
|
||||||
|
commands: ['POLL_EMAIL'],
|
||||||
|
},
|
||||||
|
'content/mail-2925': {
|
||||||
|
sourceId: 'mail-2925',
|
||||||
|
commands: ['POLL_EMAIL'],
|
||||||
|
},
|
||||||
|
'content/duck-mail': {
|
||||||
|
sourceId: 'duck-mail',
|
||||||
|
commands: ['FETCH_ALIAS_EMAIL'],
|
||||||
|
},
|
||||||
|
'content/sub2api-panel': {
|
||||||
|
sourceId: 'sub2api-panel',
|
||||||
|
commands: ['OPEN_PANEL', 'FETCH_OAUTH_URL', 'VERIFY_PLATFORM'],
|
||||||
|
},
|
||||||
|
'content/vps-panel': {
|
||||||
|
sourceId: 'vps-panel',
|
||||||
|
commands: ['OPEN_PANEL', 'FETCH_OAUTH_URL'],
|
||||||
|
},
|
||||||
|
'content/plus-checkout': {
|
||||||
|
sourceId: 'plus-checkout',
|
||||||
|
commands: ['CREATE_CHECKOUT', 'FILL_CHECKOUT'],
|
||||||
|
},
|
||||||
|
'content/paypal-flow': {
|
||||||
|
sourceId: 'paypal-flow',
|
||||||
|
commands: ['APPROVE_PAYPAL'],
|
||||||
|
},
|
||||||
|
'content/gopay-flow': {
|
||||||
|
sourceId: 'gopay-flow',
|
||||||
|
commands: ['APPROVE_GOPAY'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const CLEANUP_SCOPE_OWNERS = Object.freeze({
|
||||||
|
'oauth-localhost-callback': 'openai-auth',
|
||||||
|
});
|
||||||
|
|
||||||
|
const AUTH_PAGE_HOSTS = new Set(['auth0.openai.com', 'auth.openai.com', 'accounts.openai.com']);
|
||||||
|
const ENTRY_PAGE_HOSTS = new Set(['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com']);
|
||||||
|
const CHILD_FRAME_BLOCKED_SOURCES = new Set([
|
||||||
|
'qq-mail',
|
||||||
|
'mail-163',
|
||||||
|
'gmail-mail',
|
||||||
|
'mail-2925',
|
||||||
|
'inbucket-mail',
|
||||||
|
'plus-checkout',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function createSourceRegistry() {
|
||||||
|
function parseUrlSafely(rawUrl) {
|
||||||
|
if (!rawUrl) return null;
|
||||||
|
try {
|
||||||
|
return new URL(rawUrl);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSourceId(source) {
|
||||||
|
return String(source || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCanonicalSource(source) {
|
||||||
|
const normalized = normalizeSourceId(source);
|
||||||
|
if (!normalized) return '';
|
||||||
|
return SOURCE_ALIASES[normalized] || normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAliasKeysForCanonicalSource(source) {
|
||||||
|
const canonical = resolveCanonicalSource(source);
|
||||||
|
return Object.keys(SOURCE_ALIASES).filter((alias) => SOURCE_ALIASES[alias] === canonical);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceKeys(source) {
|
||||||
|
const normalized = normalizeSourceId(source);
|
||||||
|
const canonical = resolveCanonicalSource(normalized);
|
||||||
|
return Array.from(new Set([
|
||||||
|
canonical,
|
||||||
|
...getAliasKeysForCanonicalSource(canonical),
|
||||||
|
normalized,
|
||||||
|
].filter(Boolean)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceMeta(source) {
|
||||||
|
const canonical = resolveCanonicalSource(source);
|
||||||
|
const definition = SOURCE_DEFINITIONS[canonical];
|
||||||
|
if (!definition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: canonical,
|
||||||
|
aliases: getAliasKeysForCanonicalSource(canonical),
|
||||||
|
...definition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceLabel(source) {
|
||||||
|
return getSourceMeta(source)?.label || normalizeSourceId(source) || '未知来源';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDriverIdForSource(source) {
|
||||||
|
return getSourceMeta(source)?.driverId || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDriverMeta(sourceOrDriverId) {
|
||||||
|
const directDriverId = normalizeSourceId(sourceOrDriverId);
|
||||||
|
const driverId = Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, directDriverId)
|
||||||
|
? directDriverId
|
||||||
|
: getDriverIdForSource(sourceOrDriverId);
|
||||||
|
if (!driverId || !Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, driverId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: driverId,
|
||||||
|
...DRIVER_DEFINITIONS[driverId],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupPageHost(hostname = '') {
|
||||||
|
return AUTH_PAGE_HOSTS.has(String(hostname || '').toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupEntryHost(hostname = '') {
|
||||||
|
return ENTRY_PAGE_HOSTS.has(String(hostname || '').toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function is163MailHost(hostname = '') {
|
||||||
|
const normalized = String(hostname || '').toLowerCase();
|
||||||
|
return normalized === 'mail.163.com'
|
||||||
|
|| normalized.endsWith('.mail.163.com')
|
||||||
|
|| normalized === 'mail.126.com'
|
||||||
|
|| normalized.endsWith('.mail.126.com')
|
||||||
|
|| normalized === 'webmail.vip.163.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) {
|
||||||
|
const candidate = parseUrlSafely(candidateUrl);
|
||||||
|
if (!candidate) return false;
|
||||||
|
|
||||||
|
const canonical = resolveCanonicalSource(source);
|
||||||
|
const reference = parseUrlSafely(referenceUrl);
|
||||||
|
|
||||||
|
switch (canonical) {
|
||||||
|
case 'openai-auth':
|
||||||
|
return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname);
|
||||||
|
case 'chatgpt':
|
||||||
|
return isSignupEntryHost(candidate.hostname);
|
||||||
|
case 'duck-mail':
|
||||||
|
return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/');
|
||||||
|
case 'qq-mail':
|
||||||
|
return candidate.hostname === 'mail.qq.com' || candidate.hostname === 'wx.mail.qq.com';
|
||||||
|
case 'mail-163':
|
||||||
|
return is163MailHost(candidate.hostname);
|
||||||
|
case 'gmail-mail':
|
||||||
|
return candidate.hostname === 'mail.google.com';
|
||||||
|
case 'icloud-mail':
|
||||||
|
return candidate.hostname === 'www.icloud.com'
|
||||||
|
|| candidate.hostname === 'www.icloud.com.cn';
|
||||||
|
case 'inbucket-mail':
|
||||||
|
return Boolean(reference)
|
||||||
|
&& candidate.origin === reference.origin
|
||||||
|
&& candidate.pathname.startsWith('/m/');
|
||||||
|
case 'mail-2925':
|
||||||
|
return candidate.hostname === '2925.com' || candidate.hostname === 'www.2925.com';
|
||||||
|
case 'vps-panel':
|
||||||
|
return Boolean(reference)
|
||||||
|
&& candidate.origin === reference.origin
|
||||||
|
&& candidate.pathname === reference.pathname;
|
||||||
|
case 'sub2api-panel':
|
||||||
|
return Boolean(reference)
|
||||||
|
&& candidate.origin === reference.origin
|
||||||
|
&& (
|
||||||
|
candidate.pathname.startsWith('/admin/accounts')
|
||||||
|
|| candidate.pathname.startsWith('/login')
|
||||||
|
|| candidate.pathname === '/'
|
||||||
|
);
|
||||||
|
case 'codex2api-panel':
|
||||||
|
return Boolean(reference)
|
||||||
|
&& candidate.origin === reference.origin
|
||||||
|
&& (
|
||||||
|
candidate.pathname.startsWith('/admin/accounts')
|
||||||
|
|| candidate.pathname === '/admin'
|
||||||
|
|| candidate.pathname === '/'
|
||||||
|
);
|
||||||
|
case 'plus-checkout':
|
||||||
|
return candidate.hostname === 'chatgpt.com'
|
||||||
|
&& candidate.pathname.startsWith('/checkout/');
|
||||||
|
case 'paypal-flow':
|
||||||
|
return candidate.hostname.endsWith('paypal.com');
|
||||||
|
case 'gopay-flow':
|
||||||
|
return /gopay|gojek/i.test(candidate.hostname);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectSourceFromLocation({
|
||||||
|
injectedSource,
|
||||||
|
url = '',
|
||||||
|
hostname = '',
|
||||||
|
} = {}) {
|
||||||
|
if (injectedSource) return resolveCanonicalSource(injectedSource);
|
||||||
|
|
||||||
|
const normalizedHostname = String(hostname || '').toLowerCase();
|
||||||
|
const normalizedUrl = String(url || '');
|
||||||
|
|
||||||
|
if (isSignupPageHost(normalizedHostname)) return 'openai-auth';
|
||||||
|
if (normalizedHostname === 'mail.qq.com' || normalizedHostname === 'wx.mail.qq.com') return 'qq-mail';
|
||||||
|
if (is163MailHost(normalizedHostname)) return 'mail-163';
|
||||||
|
if (normalizedHostname === 'mail.google.com') return 'gmail-mail';
|
||||||
|
if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail';
|
||||||
|
if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
|
||||||
|
if (normalizedUrl.includes('2925.com')) return 'mail-2925';
|
||||||
|
if (isSignupEntryHost(normalizedHostname)) return 'chatgpt';
|
||||||
|
return 'unknown-source';
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldReportReadyForFrame(source, isChildFrame) {
|
||||||
|
const canonical = resolveCanonicalSource(source);
|
||||||
|
const readyPolicy = getSourceMeta(canonical)?.readyPolicy || 'allow-child-frame';
|
||||||
|
if (readyPolicy === 'disabled') return false;
|
||||||
|
if (!isChildFrame) return true;
|
||||||
|
if (readyPolicy === 'top-frame-only') return false;
|
||||||
|
if (CHILD_FRAME_BLOCKED_SOURCES.has(canonical)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCleanupOwnerSource(cleanupScope) {
|
||||||
|
return resolveCanonicalSource(CLEANUP_SCOPE_OWNERS[String(cleanupScope || '').trim()] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
detectSourceFromLocation,
|
||||||
|
getCleanupOwnerSource,
|
||||||
|
getDriverIdForSource,
|
||||||
|
getDriverMeta,
|
||||||
|
getSourceKeys,
|
||||||
|
getSourceLabel,
|
||||||
|
getSourceMeta,
|
||||||
|
is163MailHost,
|
||||||
|
isSignupEntryHost,
|
||||||
|
isSignupPageHost,
|
||||||
|
matchesSourceUrlFamily,
|
||||||
|
parseUrlSafely,
|
||||||
|
resolveCanonicalSource,
|
||||||
|
shouldReportReadyForFrame,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createSourceRegistry,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1732,6 +1732,7 @@
|
|||||||
<script src="../mail-provider-utils.js"></script>
|
<script src="../mail-provider-utils.js"></script>
|
||||||
<script src="../hotmail-utils.js"></script>
|
<script src="../hotmail-utils.js"></script>
|
||||||
<script src="../luckmail-utils.js"></script>
|
<script src="../luckmail-utils.js"></script>
|
||||||
|
<script src="../shared/flow-capabilities.js"></script>
|
||||||
<script src="../data/step-definitions.js"></script>
|
<script src="../data/step-definitions.js"></script>
|
||||||
<script src="update-service.js"></script>
|
<script src="update-service.js"></script>
|
||||||
<script src="contribution-content-update-service.js"></script>
|
<script src="contribution-content-update-service.js"></script>
|
||||||
|
|||||||
+428
-30
@@ -517,6 +517,7 @@ const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL;
|
|||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
|
||||||
|
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||||
let currentPlusModeEnabled = false;
|
let currentPlusModeEnabled = false;
|
||||||
let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD;
|
let currentPlusPaymentMethod = DEFAULT_PLUS_PAYMENT_METHOD;
|
||||||
let currentSignupMethod = DEFAULT_SIGNUP_METHOD;
|
let currentSignupMethod = DEFAULT_SIGNUP_METHOD;
|
||||||
@@ -1931,6 +1932,39 @@ function shouldWarnCpaPhoneSignup(signupMethod = null, panelMode = null) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: resolvedPanelMode,
|
||||||
|
signupMethod: resolvedSignupMethod,
|
||||||
|
state: {
|
||||||
|
...(typeof latestState !== 'undefined' ? latestState : {}),
|
||||||
|
panelMode: resolvedPanelMode,
|
||||||
|
signupMethod: resolvedSignupMethod,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: typeof latestState !== 'undefined' ? latestState?.activeFlowId : '',
|
||||||
|
panelMode: resolvedPanelMode,
|
||||||
|
signupMethod: resolvedSignupMethod,
|
||||||
|
state: {
|
||||||
|
...(typeof latestState !== 'undefined' ? latestState : {}),
|
||||||
|
panelMode: resolvedPanelMode,
|
||||||
|
signupMethod: resolvedSignupMethod,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (capabilityState && typeof capabilityState.shouldWarnCpaPhoneSignup === 'boolean') {
|
||||||
|
return capabilityState.shouldWarnCpaPhoneSignup && !isCpaPhoneSignupPromptDismissed();
|
||||||
|
}
|
||||||
|
|
||||||
return resolvedSignupMethod === SIGNUP_METHOD_PHONE
|
return resolvedSignupMethod === SIGNUP_METHOD_PHONE
|
||||||
&& resolvedPanelMode === 'cpa'
|
&& resolvedPanelMode === 'cpa'
|
||||||
&& !isCpaPhoneSignupPromptDismissed();
|
&& !isCpaPhoneSignupPromptDismissed();
|
||||||
@@ -3470,6 +3504,57 @@ function collectSettingsPayload() {
|
|||||||
? normalizeSignupMethod(latestState?.signupMethod)
|
? normalizeSignupMethod(latestState?.signupMethod)
|
||||||
: (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'))
|
: (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'))
|
||||||
);
|
);
|
||||||
|
const normalizePanelModeSafe = typeof normalizePanelMode === 'function'
|
||||||
|
? normalizePanelMode
|
||||||
|
: ((value = '') => {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
|
||||||
|
});
|
||||||
|
const rawPanelMode = normalizePanelModeSafe(selectPanelMode?.value || latestState?.panelMode || 'cpa');
|
||||||
|
const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
|
: Boolean(latestState?.plusModeEnabled);
|
||||||
|
const rawPhoneVerificationEnabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: rawPanelMode,
|
||||||
|
signupMethod: selectedSignupMethod,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
panelMode: rawPanelMode,
|
||||||
|
plusModeEnabled: rawPlusModeEnabled,
|
||||||
|
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
||||||
|
signupMethod: selectedSignupMethod,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: latestState?.activeFlowId,
|
||||||
|
panelMode: rawPanelMode,
|
||||||
|
signupMethod: selectedSignupMethod,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
panelMode: rawPanelMode,
|
||||||
|
plusModeEnabled: rawPlusModeEnabled,
|
||||||
|
phoneVerificationEnabled: rawPhoneVerificationEnabled,
|
||||||
|
signupMethod: selectedSignupMethod,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
const effectivePanelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode;
|
||||||
|
const effectivePlusModeEnabled = capabilityState
|
||||||
|
? Boolean(capabilityState.runtimeLocks?.plusModeEnabled)
|
||||||
|
: rawPlusModeEnabled;
|
||||||
|
const effectivePhoneVerificationEnabled = capabilityState
|
||||||
|
? Boolean(capabilityState.runtimeLocks?.phoneVerificationEnabled)
|
||||||
|
: rawPhoneVerificationEnabled;
|
||||||
|
const effectiveSignupMethod = capabilityState?.effectiveSignupMethod || selectedSignupMethod;
|
||||||
const plusPaymentMethod = getSelectedPlusPaymentMethod();
|
const plusPaymentMethod = getSelectedPlusPaymentMethod();
|
||||||
const normalizeGpcHelperPhoneModeSafe = typeof normalizeGpcHelperPhoneModeValue === 'function'
|
const normalizeGpcHelperPhoneModeSafe = typeof normalizeGpcHelperPhoneModeValue === 'function'
|
||||||
? normalizeGpcHelperPhoneModeValue
|
? normalizeGpcHelperPhoneModeValue
|
||||||
@@ -3527,7 +3612,7 @@ function collectSettingsPayload() {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...(contributionModeEnabled ? {} : {
|
...(contributionModeEnabled ? {} : {
|
||||||
panelMode: selectPanelMode.value,
|
panelMode: effectivePanelMode,
|
||||||
}),
|
}),
|
||||||
vpsUrl: inputVpsUrl.value.trim(),
|
vpsUrl: inputVpsUrl.value.trim(),
|
||||||
vpsPassword: inputVpsPassword.value,
|
vpsPassword: inputVpsPassword.value,
|
||||||
@@ -3565,9 +3650,7 @@ function collectSettingsPayload() {
|
|||||||
ipProxyRegion: currentIpProxyServiceProfile.region,
|
ipProxyRegion: currentIpProxyServiceProfile.region,
|
||||||
codex2apiUrl: inputCodex2ApiUrl.value.trim(),
|
codex2apiUrl: inputCodex2ApiUrl.value.trim(),
|
||||||
codex2apiAdminKey: inputCodex2ApiAdminKey.value.trim(),
|
codex2apiAdminKey: inputCodex2ApiAdminKey.value.trim(),
|
||||||
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
plusModeEnabled: effectivePlusModeEnabled,
|
||||||
? Boolean(inputPlusModeEnabled.checked)
|
|
||||||
: Boolean(latestState?.plusModeEnabled),
|
|
||||||
plusPaymentMethod,
|
plusPaymentMethod,
|
||||||
paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(),
|
paypalEmail: String(currentPayPalAccount?.email || latestState?.paypalEmail || '').trim(),
|
||||||
paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''),
|
paypalPassword: String(currentPayPalAccount?.password || latestState?.paypalPassword || ''),
|
||||||
@@ -3682,8 +3765,8 @@ function collectSettingsPayload() {
|
|||||||
oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled
|
oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled
|
||||||
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
|
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
|
||||||
: true,
|
: true,
|
||||||
phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled?.checked),
|
phoneVerificationEnabled: effectivePhoneVerificationEnabled,
|
||||||
signupMethod: selectedSignupMethod,
|
signupMethod: effectiveSignupMethod,
|
||||||
phoneSmsProvider: phoneSmsProviderValue,
|
phoneSmsProvider: phoneSmsProviderValue,
|
||||||
phoneSmsProviderOrder: phoneSmsProviderOrderValue,
|
phoneSmsProviderOrder: phoneSmsProviderOrderValue,
|
||||||
verificationResendCount: normalizeVerificationResendCount(
|
verificationResendCount: normalizeVerificationResendCount(
|
||||||
@@ -7215,11 +7298,66 @@ function normalizePanelMode(value = '') {
|
|||||||
return 'cpa';
|
return 'cpa';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let flowCapabilityRegistry = null;
|
||||||
|
|
||||||
|
function getFlowCapabilityRegistry() {
|
||||||
|
if (flowCapabilityRegistry) {
|
||||||
|
return flowCapabilityRegistry;
|
||||||
|
}
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
flowCapabilityRegistry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||||
|
}) || null;
|
||||||
|
return flowCapabilityRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCurrentSidepanelCapabilities(options = {}) {
|
||||||
|
const registry = getFlowCapabilityRegistry();
|
||||||
|
if (!registry?.resolveSidepanelCapabilities) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const state = {
|
||||||
|
...(latestState || {}),
|
||||||
|
...(options?.state || {}),
|
||||||
|
};
|
||||||
|
return registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: options?.activeFlowId ?? state?.activeFlowId,
|
||||||
|
panelMode: options?.panelMode ?? state?.panelMode,
|
||||||
|
signupMethod: options?.signupMethod ?? state?.signupMethod,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveStepDefinitionCapabilityState(state = latestState, options = {}) {
|
||||||
|
const nextState = {
|
||||||
|
...(state || {}),
|
||||||
|
...(options?.state || {}),
|
||||||
|
};
|
||||||
|
const capabilityState = resolveCurrentSidepanelCapabilities({
|
||||||
|
activeFlowId: options?.activeFlowId ?? nextState?.activeFlowId,
|
||||||
|
panelMode: options?.panelMode ?? nextState?.panelMode,
|
||||||
|
signupMethod: options?.signupMethod ?? nextState?.signupMethod,
|
||||||
|
state: nextState,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
capabilityState,
|
||||||
|
plusModeEnabled: capabilityState
|
||||||
|
? Boolean(capabilityState.runtimeLocks?.plusModeEnabled)
|
||||||
|
: Boolean(nextState?.plusModeEnabled),
|
||||||
|
signupMethod: capabilityState?.effectiveSignupMethod
|
||||||
|
|| normalizeSignupMethod((options?.signupMethod ?? nextState?.signupMethod) || DEFAULT_SIGNUP_METHOD),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedPanelMode() {
|
function getSelectedPanelMode() {
|
||||||
const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode
|
const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode
|
||||||
? selectPanelMode.value
|
? selectPanelMode.value
|
||||||
: (typeof latestState !== 'undefined' ? latestState?.panelMode : '');
|
: (typeof latestState !== 'undefined' ? latestState?.panelMode : '');
|
||||||
return normalizePanelMode(selectedValue || 'cpa');
|
const resolvedPanelMode = normalizePanelMode(selectedValue || 'cpa');
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({ panelMode: resolvedPanelMode })
|
||||||
|
: null;
|
||||||
|
return capabilityState?.effectivePanelMode || capabilityState?.panelMode || resolvedPanelMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedSignupMethod() {
|
function getSelectedSignupMethod() {
|
||||||
@@ -7244,6 +7382,37 @@ function canSelectPhoneSignupMethod() {
|
|||||||
? Boolean(inputPlusModeEnabled.checked)
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
: Boolean(latestState?.plusModeEnabled);
|
: Boolean(latestState?.plusModeEnabled);
|
||||||
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
||||||
|
state: {
|
||||||
|
...(typeof latestState !== 'undefined' ? latestState : {}),
|
||||||
|
phoneVerificationEnabled: phoneEnabled,
|
||||||
|
plusModeEnabled,
|
||||||
|
contributionMode: contributionModeEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: typeof latestState !== 'undefined' ? latestState?.activeFlowId : '',
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'),
|
||||||
|
state: {
|
||||||
|
...(typeof latestState !== 'undefined' ? latestState : {}),
|
||||||
|
phoneVerificationEnabled: phoneEnabled,
|
||||||
|
plusModeEnabled,
|
||||||
|
contributionMode: contributionModeEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
if (capabilityState && typeof capabilityState.canSelectPhoneSignup === 'boolean') {
|
||||||
|
return capabilityState.canSelectPhoneSignup;
|
||||||
|
}
|
||||||
return phoneEnabled && !plusModeEnabled && !contributionModeEnabled;
|
return phoneEnabled && !plusModeEnabled && !contributionModeEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7295,22 +7464,68 @@ function updateSignupMethodUI(options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
syncStepDefinitionsForMode(
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
? resolveStepDefinitionCapabilityState({
|
||||||
? Boolean(inputPlusModeEnabled.checked)
|
...(latestState || {}),
|
||||||
: Boolean(latestState?.plusModeEnabled),
|
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
{
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
plusPaymentMethod: getSelectedPlusPaymentMethod(latestState),
|
: Boolean(latestState?.plusModeEnabled),
|
||||||
signupMethod: selectedMethod,
|
signupMethod: selectedMethod,
|
||||||
}
|
}, {
|
||||||
);
|
signupMethod: selectedMethod,
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
|
: Boolean(latestState?.plusModeEnabled),
|
||||||
|
signupMethod: selectedMethod,
|
||||||
|
};
|
||||||
|
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, {
|
||||||
|
plusPaymentMethod: getSelectedPlusPaymentMethod(latestState),
|
||||||
|
signupMethod: selectedMethod,
|
||||||
|
});
|
||||||
if (typeof syncSignupPhoneInputFromState === 'function') {
|
if (typeof syncSignupPhoneInputFromState === 'function') {
|
||||||
syncSignupPhoneInputFromState(latestState);
|
syncSignupPhoneInputFromState(latestState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePhoneVerificationSettingsUI() {
|
function updatePhoneVerificationSettingsUI() {
|
||||||
const enabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
const rawEnabled = Boolean(inputPhoneVerificationEnabled?.checked);
|
||||||
|
const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
|
: Boolean(latestState?.plusModeEnabled);
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
||||||
|
signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
phoneVerificationEnabled: rawEnabled,
|
||||||
|
plusModeEnabled: rawPlusModeEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: latestState?.activeFlowId,
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'),
|
||||||
|
signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
phoneVerificationEnabled: rawEnabled,
|
||||||
|
plusModeEnabled: rawPlusModeEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
const canShowPhoneSettings = capabilityState
|
||||||
|
? Boolean(capabilityState.canShowPhoneSettings)
|
||||||
|
: true;
|
||||||
|
const enabled = canShowPhoneSettings && rawEnabled;
|
||||||
const showSettings = enabled && phoneVerificationSectionExpanded;
|
const showSettings = enabled && phoneVerificationSectionExpanded;
|
||||||
const normalizeProvider = typeof normalizePhoneSmsProviderValue === 'function'
|
const normalizeProvider = typeof normalizePhoneSmsProviderValue === 'function'
|
||||||
? normalizePhoneSmsProviderValue
|
? normalizePhoneSmsProviderValue
|
||||||
@@ -7333,10 +7548,10 @@ function updatePhoneVerificationSettingsUI() {
|
|||||||
const fiveSimProvider = provider === fiveSimProviderValue;
|
const fiveSimProvider = provider === fiveSimProviderValue;
|
||||||
const nexSmsProvider = provider === nexSmsProviderValue;
|
const nexSmsProvider = provider === nexSmsProviderValue;
|
||||||
if (rowPhoneVerificationEnabled) {
|
if (rowPhoneVerificationEnabled) {
|
||||||
rowPhoneVerificationEnabled.style.display = '';
|
rowPhoneVerificationEnabled.style.display = canShowPhoneSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowHeroSmsPlatform) {
|
if (rowHeroSmsPlatform) {
|
||||||
rowHeroSmsPlatform.style.display = '';
|
rowHeroSmsPlatform.style.display = canShowPhoneSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
updateSignupMethodUI();
|
updateSignupMethodUI();
|
||||||
if (btnTogglePhoneVerificationSection) {
|
if (btnTogglePhoneVerificationSection) {
|
||||||
@@ -7447,9 +7662,37 @@ function updatePlusModeUI() {
|
|||||||
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay';
|
||||||
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
const gpcValue = typeof PLUS_PAYMENT_METHOD_GPC_HELPER !== 'undefined' ? PLUS_PAYMENT_METHOD_GPC_HELPER : 'gpc-helper';
|
||||||
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
|
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
|
||||||
const enabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
const rawEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled
|
||||||
? Boolean(inputPlusModeEnabled.checked)
|
? Boolean(inputPlusModeEnabled.checked)
|
||||||
: false;
|
: false;
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
plusModeEnabled: rawEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: latestState?.activeFlowId,
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'),
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
plusModeEnabled: rawEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
const supportsPlusMode = capabilityState
|
||||||
|
? Boolean(capabilityState.canShowPlusSettings)
|
||||||
|
: true;
|
||||||
|
const enabled = supportsPlusMode && rawEnabled;
|
||||||
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod;
|
||||||
const gpcPhoneMode = normalizeGpcHelperPhoneModeValue(
|
const gpcPhoneMode = normalizeGpcHelperPhoneModeValue(
|
||||||
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode
|
||||||
@@ -7476,6 +7719,9 @@ function updatePlusModeUI() {
|
|||||||
const canShowGpcModeSelector = gpcRowsVisible;
|
const canShowGpcModeSelector = gpcRowsVisible;
|
||||||
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode;
|
||||||
const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled;
|
const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled;
|
||||||
|
if (typeof rowPlusMode !== 'undefined' && rowPlusMode) {
|
||||||
|
rowPlusMode.style.display = supportsPlusMode ? '' : 'none';
|
||||||
|
}
|
||||||
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) {
|
||||||
selectPlusPaymentMethod.value = method;
|
selectPlusPaymentMethod.value = method;
|
||||||
if (selectPlusPaymentMethod.style) {
|
if (selectPlusPaymentMethod.style) {
|
||||||
@@ -7657,7 +7903,39 @@ function syncSignupPhoneInputFromState(state = latestState) {
|
|||||||
const selectedMethod = typeof normalizeSignupMethod === 'function'
|
const selectedMethod = typeof normalizeSignupMethod === 'function'
|
||||||
? normalizeSignupMethod(rawSignupMethod)
|
? normalizeSignupMethod(rawSignupMethod)
|
||||||
: (String(rawSignupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email');
|
: (String(rawSignupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email');
|
||||||
rowSignupPhone.style.display = phoneVerificationEnabled
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: state?.panelMode || latestState?.panelMode,
|
||||||
|
signupMethod: selectedMethod,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
...(state || {}),
|
||||||
|
phoneVerificationEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: (() => {
|
||||||
|
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||||
|
const registry = rootScope.MultiPageFlowCapabilities?.createFlowCapabilityRegistry?.({
|
||||||
|
defaultFlowId: typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai',
|
||||||
|
}) || null;
|
||||||
|
return registry?.resolveSidepanelCapabilities
|
||||||
|
? registry.resolveSidepanelCapabilities({
|
||||||
|
activeFlowId: state?.activeFlowId || latestState?.activeFlowId,
|
||||||
|
panelMode: state?.panelMode || latestState?.panelMode,
|
||||||
|
signupMethod: selectedMethod,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
...(state || {}),
|
||||||
|
phoneVerificationEnabled,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
})();
|
||||||
|
const canShowPhoneSettings = capabilityState
|
||||||
|
? Boolean(capabilityState.canShowPhoneSettings)
|
||||||
|
: true;
|
||||||
|
rowSignupPhone.style.display = canShowPhoneSettings
|
||||||
|
&& phoneVerificationEnabled
|
||||||
&& (selectedMethod === 'phone' || Boolean(signupPhone) || Boolean(getSignupPhoneInputValue()) || signupPhoneInputDirty)
|
&& (selectedMethod === 'phone' || Boolean(signupPhone) || Boolean(getSignupPhoneInputValue()) || signupPhoneInputDirty)
|
||||||
? ''
|
? ''
|
||||||
: 'none';
|
: 'none';
|
||||||
@@ -8285,9 +8563,17 @@ function syncStepDefinitionsForMode(plusModeEnabled = false, plusPaymentMethodOr
|
|||||||
|
|
||||||
function applySettingsState(state) {
|
function applySettingsState(state) {
|
||||||
if (typeof syncStepDefinitionsForMode === 'function') {
|
if (typeof syncStepDefinitionsForMode === 'function') {
|
||||||
syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled), {
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
|
? resolveStepDefinitionCapabilityState(state, {
|
||||||
|
signupMethod: state?.signupMethod,
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: Boolean(state?.plusModeEnabled),
|
||||||
|
signupMethod: normalizeSignupMethod(state?.signupMethod || DEFAULT_SIGNUP_METHOD),
|
||||||
|
};
|
||||||
|
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, {
|
||||||
plusPaymentMethod: state?.plusPaymentMethod,
|
plusPaymentMethod: state?.plusPaymentMethod,
|
||||||
signupMethod: state?.signupMethod,
|
signupMethod: stepDefinitionState.signupMethod,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const fallbackIpProxyService = '711proxy';
|
const fallbackIpProxyService = '711proxy';
|
||||||
@@ -9776,6 +10062,30 @@ function updateMailProviderUI() {
|
|||||||
const icloudHostPreferenceValue = typeof selectIcloudHostPreference !== 'undefined'
|
const icloudHostPreferenceValue = typeof selectIcloudHostPreference !== 'undefined'
|
||||||
? selectIcloudHostPreference?.value
|
? selectIcloudHostPreference?.value
|
||||||
: latestState?.icloudHostPreference;
|
: latestState?.icloudHostPreference;
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode,
|
||||||
|
state: latestState || {},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
const canShowLuckmail = capabilityState
|
||||||
|
? Boolean(capabilityState.canShowLuckmail)
|
||||||
|
: true;
|
||||||
|
const mailProviderOptions = Array.from(selectMailProvider?.options || []);
|
||||||
|
mailProviderOptions.forEach((option) => {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String(option.value || '').trim().toLowerCase() === 'luckmail-api') {
|
||||||
|
option.hidden = !canShowLuckmail;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!canShowLuckmail && String(selectMailProvider?.value || '').trim().toLowerCase() === 'luckmail-api') {
|
||||||
|
const fallbackOption = mailProviderOptions.find((option) => option && !option.hidden);
|
||||||
|
if (fallbackOption) {
|
||||||
|
selectMailProvider.value = String(fallbackOption.value || '').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
const use2925 = selectMailProvider.value === '2925';
|
const use2925 = selectMailProvider.value === '2925';
|
||||||
const useGmail = selectMailProvider.value === GMAIL_PROVIDER;
|
const useGmail = selectMailProvider.value === GMAIL_PROVIDER;
|
||||||
const useMail2925 = selectMailProvider.value === '2925';
|
const useMail2925 = selectMailProvider.value === '2925';
|
||||||
@@ -9806,7 +10116,7 @@ function updateMailProviderUI() {
|
|||||||
const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode, selectedGenerator);
|
const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode, selectedGenerator);
|
||||||
const useInbucket = selectMailProvider.value === 'inbucket';
|
const useInbucket = selectMailProvider.value === 'inbucket';
|
||||||
const useHotmail = selectMailProvider.value === 'hotmail-api';
|
const useHotmail = selectMailProvider.value === 'hotmail-api';
|
||||||
const useLuckmail = isLuckmailProvider();
|
const useLuckmail = canShowLuckmail && isLuckmailProvider();
|
||||||
const useCustomEmail = isCustomMailProvider();
|
const useCustomEmail = isCustomMailProvider();
|
||||||
const useCustomMailProviderPool = useCustomEmail && usesCustomMailProviderPool(selectMailProvider.value);
|
const useCustomMailProviderPool = useCustomEmail && usesCustomMailProviderPool(selectMailProvider.value);
|
||||||
const useIcloudProvider = isIcloudMailProvider();
|
const useIcloudProvider = isIcloudMailProvider();
|
||||||
@@ -10228,7 +10538,39 @@ async function handleDeleteSub2ApiGroup(groupName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updatePanelModeUI() {
|
function updatePanelModeUI() {
|
||||||
const panelMode = getSelectedPanelMode();
|
const rawPanelMode = normalizePanelMode(selectPanelMode?.value || latestState?.panelMode || 'cpa');
|
||||||
|
const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function'
|
||||||
|
? resolveCurrentSidepanelCapabilities({
|
||||||
|
panelMode: rawPanelMode,
|
||||||
|
state: {
|
||||||
|
...(latestState || {}),
|
||||||
|
panelMode: rawPanelMode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
const supportedPanelModes = Array.isArray(capabilityState?.supportedPanelModes)
|
||||||
|
? capabilityState.supportedPanelModes
|
||||||
|
: [];
|
||||||
|
if (selectPanelMode?.options && supportedPanelModes.length) {
|
||||||
|
Array.from(selectPanelMode.options).forEach((option) => {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const optionMode = normalizePanelMode(option.value || '');
|
||||||
|
const enabled = supportedPanelModes.includes(optionMode);
|
||||||
|
option.disabled = !enabled;
|
||||||
|
option.hidden = !enabled;
|
||||||
|
});
|
||||||
|
} else if (selectPanelMode?.options) {
|
||||||
|
Array.from(selectPanelMode.options).forEach((option) => {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
option.disabled = false;
|
||||||
|
option.hidden = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const panelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || getSelectedPanelMode();
|
||||||
if (selectPanelMode) {
|
if (selectPanelMode) {
|
||||||
selectPanelMode.value = panelMode;
|
selectPanelMode.value = panelMode;
|
||||||
}
|
}
|
||||||
@@ -11799,7 +12141,22 @@ inputPassword.addEventListener('blur', () => {
|
|||||||
inputPlusModeEnabled?.addEventListener('change', () => {
|
inputPlusModeEnabled?.addEventListener('change', () => {
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
updateSignupMethodUI({ notify: true });
|
updateSignupMethodUI({ notify: true });
|
||||||
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled.checked), getSelectedPlusPaymentMethod(), { render: true });
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
|
? resolveStepDefinitionCapabilityState({
|
||||||
|
...(latestState || {}),
|
||||||
|
plusModeEnabled: Boolean(inputPlusModeEnabled.checked),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
}, {
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: Boolean(inputPlusModeEnabled.checked),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
};
|
||||||
|
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, getSelectedPlusPaymentMethod(), {
|
||||||
|
render: true,
|
||||||
|
signupMethod: stepDefinitionState.signupMethod,
|
||||||
|
});
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
@@ -11811,7 +12168,22 @@ inputOperationDelayEnabled?.addEventListener('change', () => {
|
|||||||
selectPlusPaymentMethod?.addEventListener('change', () => {
|
selectPlusPaymentMethod?.addEventListener('change', () => {
|
||||||
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
|
selectPlusPaymentMethod.value = normalizePlusPaymentMethod(selectPlusPaymentMethod.value);
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled?.checked), selectPlusPaymentMethod.value, { render: true });
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
|
? resolveStepDefinitionCapabilityState({
|
||||||
|
...(latestState || {}),
|
||||||
|
plusModeEnabled: Boolean(inputPlusModeEnabled?.checked),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
}, {
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: Boolean(inputPlusModeEnabled?.checked),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
};
|
||||||
|
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, selectPlusPaymentMethod.value, {
|
||||||
|
render: true,
|
||||||
|
signupMethod: stepDefinitionState.signupMethod,
|
||||||
|
});
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
@@ -11873,9 +12245,22 @@ btnGpcHelperBalance?.addEventListener('click', async () => {
|
|||||||
|
|
||||||
selectPlusPaymentMethod?.addEventListener('change', () => {
|
selectPlusPaymentMethod?.addEventListener('change', () => {
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
syncStepDefinitionsForMode(Boolean(inputPlusModeEnabled?.checked), {
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
|
? resolveStepDefinitionCapabilityState({
|
||||||
|
...(latestState || {}),
|
||||||
|
plusModeEnabled: Boolean(inputPlusModeEnabled?.checked),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
}, {
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: Boolean(inputPlusModeEnabled?.checked),
|
||||||
|
signupMethod: getSelectedSignupMethod(),
|
||||||
|
};
|
||||||
|
syncStepDefinitionsForMode(stepDefinitionState.plusModeEnabled, {
|
||||||
render: true,
|
render: true,
|
||||||
plusPaymentMethod: selectPlusPaymentMethod.value,
|
plusPaymentMethod: selectPlusPaymentMethod.value,
|
||||||
|
signupMethod: stepDefinitionState.signupMethod,
|
||||||
});
|
});
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
@@ -12011,7 +12396,9 @@ checkboxAutoDeleteIcloud?.addEventListener('change', () => {
|
|||||||
|
|
||||||
selectPanelMode.addEventListener('change', async () => {
|
selectPanelMode.addEventListener('change', async () => {
|
||||||
const previousPanelMode = normalizePanelMode(latestState?.panelMode || 'cpa');
|
const previousPanelMode = normalizePanelMode(latestState?.panelMode || 'cpa');
|
||||||
const nextPanelMode = normalizePanelMode(selectPanelMode.value);
|
const rawNextPanelMode = normalizePanelMode(selectPanelMode.value);
|
||||||
|
selectPanelMode.value = rawNextPanelMode;
|
||||||
|
const nextPanelMode = getSelectedPanelMode();
|
||||||
selectPanelMode.value = nextPanelMode;
|
selectPanelMode.value = nextPanelMode;
|
||||||
const confirmed = await confirmCpaPhoneSignupIfNeeded({
|
const confirmed = await confirmCpaPhoneSignupIfNeeded({
|
||||||
signupMethod: getSelectedSignupMethod(),
|
signupMethod: getSelectedSignupMethod(),
|
||||||
@@ -13845,10 +14232,21 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
|| message.payload.gopayHelperOtpChannel !== undefined
|
|| message.payload.gopayHelperOtpChannel !== undefined
|
||||||
|| message.payload.gopayHelperLocalSmsHelperEnabled !== undefined
|
|| message.payload.gopayHelperLocalSmsHelperEnabled !== undefined
|
||||||
) {
|
) {
|
||||||
|
const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function'
|
||||||
|
? resolveStepDefinitionCapabilityState(latestState, {
|
||||||
|
signupMethod: latestState?.signupMethod,
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
plusModeEnabled: Boolean(latestState?.plusModeEnabled),
|
||||||
|
signupMethod: normalizeSignupMethod(latestState?.signupMethod || DEFAULT_SIGNUP_METHOD),
|
||||||
|
};
|
||||||
syncStepDefinitionsForMode(
|
syncStepDefinitionsForMode(
|
||||||
Boolean(latestState?.plusModeEnabled),
|
stepDefinitionState.plusModeEnabled,
|
||||||
latestState?.plusPaymentMethod,
|
latestState?.plusPaymentMethod,
|
||||||
{ render: true }
|
{
|
||||||
|
render: true,
|
||||||
|
signupMethod: stepDefinitionState.signupMethod,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
updatePlusModeUI();
|
updatePlusModeUI();
|
||||||
updateSignupMethodUI({ notify: true });
|
updateSignupMethodUI({ notify: true });
|
||||||
|
|||||||
@@ -731,6 +731,9 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
|||||||
'async function getPersistedAliasState() {',
|
'async function getPersistedAliasState() {',
|
||||||
' return {};',
|
' return {};',
|
||||||
'}',
|
'}',
|
||||||
|
'function buildStatePatchWithRuntimeState(_currentState, updates) {',
|
||||||
|
' return updates;',
|
||||||
|
'}',
|
||||||
'const chrome = {',
|
'const chrome = {',
|
||||||
' storage: {',
|
' storage: {',
|
||||||
' session: {',
|
' session: {',
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
test('background imports mail rule registry and OpenAI mail rules modules', () => {
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
assert.match(source, /background\/mail-rule-registry\.js/);
|
||||||
|
assert.match(source, /flows\/openai\/mail-rules\.js/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mail rule registry exposes canonical OpenAI verification poll payloads', () => {
|
||||||
|
const registrySource = fs.readFileSync('background/mail-rule-registry.js', 'utf8');
|
||||||
|
const openAiSource = fs.readFileSync('flows/openai/mail-rules.js', 'utf8');
|
||||||
|
const registryApi = new Function('self', `${registrySource}; return self.MultiPageBackgroundMailRuleRegistry;`)({});
|
||||||
|
const openAiApi = new Function('self', `${openAiSource}; return self.MultiPageOpenAiMailRules;`)({});
|
||||||
|
|
||||||
|
const openAiMailRules = openAiApi.createOpenAiMailRules({
|
||||||
|
getHotmailVerificationRequestTimestamp: (step) => (step === 4 ? 123 : 456),
|
||||||
|
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||||
|
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||||
|
});
|
||||||
|
const registry = registryApi.createMailRuleRegistry({
|
||||||
|
defaultFlowId: 'openai',
|
||||||
|
flowBuilders: {
|
||||||
|
openai: openAiMailRules,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
registry.buildVerificationPollPayload(
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
email: 'user@example.com',
|
||||||
|
mailProvider: '2925',
|
||||||
|
mail2925Mode: 'receive',
|
||||||
|
},
|
||||||
|
{ excludeCodes: ['111111'] }
|
||||||
|
),
|
||||||
|
{
|
||||||
|
flowId: 'openai',
|
||||||
|
ruleId: 'openai-signup-code',
|
||||||
|
step: 4,
|
||||||
|
artifactType: 'code',
|
||||||
|
filterAfterTimestamp: 0,
|
||||||
|
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
|
||||||
|
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
|
||||||
|
targetEmail: 'user@example.com',
|
||||||
|
mail2925MatchTargetEmail: true,
|
||||||
|
maxAttempts: 15,
|
||||||
|
intervalMs: 15000,
|
||||||
|
excludeCodes: ['111111'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
registry.buildVerificationPollPayload(8, {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
email: 'user@example.com',
|
||||||
|
step8VerificationTargetEmail: 'login@example.com',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
flowId: 'openai',
|
||||||
|
ruleId: 'openai-login-code',
|
||||||
|
step: 8,
|
||||||
|
artifactType: 'code',
|
||||||
|
filterAfterTimestamp: 456,
|
||||||
|
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||||
|
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||||
|
targetEmail: 'login@example.com',
|
||||||
|
mail2925MatchTargetEmail: false,
|
||||||
|
maxAttempts: 5,
|
||||||
|
intervalMs: 3000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mail rule registry rejects unknown active flow ids instead of silently using OpenAI rules', () => {
|
||||||
|
const registrySource = fs.readFileSync('background/mail-rule-registry.js', 'utf8');
|
||||||
|
const registryApi = new Function('self', `${registrySource}; return self.MultiPageBackgroundMailRuleRegistry;`)({});
|
||||||
|
const registry = registryApi.createMailRuleRegistry({
|
||||||
|
defaultFlowId: 'openai',
|
||||||
|
flowBuilders: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => registry.buildVerificationPollPayload(4, {
|
||||||
|
activeFlowId: 'site-a',
|
||||||
|
email: 'user@example.com',
|
||||||
|
}),
|
||||||
|
/未找到 flow=site-a 的邮件轮询规则构造器/
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -162,3 +162,39 @@ test('SAVE_SETTING broadcasts operation delay setting without background success
|
|||||||
assert.deepStrictEqual(broadcasts.at(-1), { operationDelayEnabled: false });
|
assert.deepStrictEqual(broadcasts.at(-1), { operationDelayEnabled: false });
|
||||||
assert.equal(logs.length, 0);
|
assert.equal(logs.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('SAVE_SETTING re-resolves signup method when panel mode changes', async () => {
|
||||||
|
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||||
|
const globalScope = { console };
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||||
|
let state = {
|
||||||
|
signupMethod: 'phone',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
plusModeEnabled: false,
|
||||||
|
panelMode: 'sub2api',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = api.createMessageRouter({
|
||||||
|
addLog: async () => {},
|
||||||
|
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||||
|
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
|
||||||
|
? { panelMode: input.panelMode }
|
||||||
|
: {},
|
||||||
|
broadcastDataUpdate: () => {},
|
||||||
|
getState: async () => ({ ...state }),
|
||||||
|
resolveSignupMethod: (nextState = {}) => nextState.panelMode === 'cpa' ? 'email' : 'phone',
|
||||||
|
setPersistentSettings: async () => {},
|
||||||
|
setState: async (updates) => {
|
||||||
|
state = { ...state, ...updates };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await router.handleMessage({
|
||||||
|
type: 'SAVE_SETTING',
|
||||||
|
payload: { panelMode: 'cpa' },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(response.ok, true);
|
||||||
|
assert.equal(state.panelMode, 'cpa');
|
||||||
|
assert.equal(state.signupMethod, 'email');
|
||||||
|
});
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ test('navigation utils recognize signup password pages for email and phone signu
|
|||||||
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/create-account/password'), true);
|
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/create-account/password'), true);
|
||||||
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/log-in/password'), true);
|
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/log-in/password'), true);
|
||||||
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/log-in'), false);
|
assert.equal(utils.isSignupPasswordPageUrl('https://auth.openai.com/log-in'), false);
|
||||||
|
assert.equal(utils.isSignupEntryHost('www.chatgpt.com'), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('navigation utils treat 126 mail hosts as part of the shared NetEase mail family', () => {
|
test('navigation utils treat 126 mail hosts as part of the shared NetEase mail family', () => {
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
function loadRuntimeStateApi() {
|
||||||
|
const source = fs.readFileSync('background/runtime-state.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
return new Function('self', `${source}; return self.MultiPageBackgroundRuntimeState;`)(globalScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('background imports runtime-state module and wires state view helpers', () => {
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
assert.match(source, /background\/runtime-state\.js/);
|
||||||
|
assert.match(source, /createRuntimeStateHelpers/);
|
||||||
|
assert.match(source, /buildStateViewWithRuntimeState/);
|
||||||
|
assert.match(source, /buildStatePatchWithRuntimeState/);
|
||||||
|
assert.match(source, /runtimeState:/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runtime-state module exposes a factory', () => {
|
||||||
|
const api = loadRuntimeStateApi();
|
||||||
|
assert.equal(typeof api?.createRuntimeStateHelpers, 'function');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runtime-state view derives canonical flow metadata from legacy step state', () => {
|
||||||
|
const api = loadRuntimeStateApi();
|
||||||
|
const helpers = api.createRuntimeStateHelpers({
|
||||||
|
DEFAULT_ACTIVE_FLOW_ID: 'openai',
|
||||||
|
defaultStepStatuses: {
|
||||||
|
1: 'pending',
|
||||||
|
2: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
getStepDefinitionForState(step) {
|
||||||
|
return {
|
||||||
|
1: { id: 1, key: 'open-chatgpt' },
|
||||||
|
2: { id: 2, key: 'submit-signup-email' },
|
||||||
|
10: { id: 10, key: 'oauth-login' },
|
||||||
|
}[Number(step)] || null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const view = helpers.buildStateView({
|
||||||
|
currentStep: 2,
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'completed',
|
||||||
|
2: 'running',
|
||||||
|
},
|
||||||
|
oauthUrl: 'https://auth.example.com/start',
|
||||||
|
plusCheckoutTabId: 88,
|
||||||
|
currentPhoneActivation: {
|
||||||
|
activationId: 'active-1',
|
||||||
|
phoneNumber: '+447700900123',
|
||||||
|
},
|
||||||
|
tabRegistry: {
|
||||||
|
'signup-page': { tabId: 12 },
|
||||||
|
},
|
||||||
|
sourceLastUrls: {
|
||||||
|
'signup-page': 'https://auth.example.com/start',
|
||||||
|
},
|
||||||
|
flowStartTime: 12345,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(view.activeFlowId, 'openai');
|
||||||
|
assert.equal(view.currentNodeId, 'submit-signup-email');
|
||||||
|
assert.deepStrictEqual(view.legacyStepCompat, {
|
||||||
|
currentStep: 2,
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'completed',
|
||||||
|
2: 'running',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(view.nodeStatuses, {
|
||||||
|
'open-chatgpt': 'completed',
|
||||||
|
'submit-signup-email': 'running',
|
||||||
|
'oauth-login': 'pending',
|
||||||
|
});
|
||||||
|
assert.equal(view.runtimeState.flowState.openai.auth.oauthUrl, 'https://auth.example.com/start');
|
||||||
|
assert.equal(view.runtimeState.flowState.openai.plus.plusCheckoutTabId, 88);
|
||||||
|
assert.deepStrictEqual(view.runtimeState.flowState.openai.phoneVerification.currentPhoneActivation, {
|
||||||
|
activationId: 'active-1',
|
||||||
|
phoneNumber: '+447700900123',
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(view.sharedState, {
|
||||||
|
tabRegistry: {
|
||||||
|
'signup-page': { tabId: 12 },
|
||||||
|
},
|
||||||
|
sourceLastUrls: {
|
||||||
|
'signup-page': 'https://auth.example.com/start',
|
||||||
|
},
|
||||||
|
flowStartTime: 12345,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runtime-state patch accepts nested flow updates while keeping legacy compatibility fields in sync', () => {
|
||||||
|
const api = loadRuntimeStateApi();
|
||||||
|
const helpers = api.createRuntimeStateHelpers({
|
||||||
|
DEFAULT_ACTIVE_FLOW_ID: 'openai',
|
||||||
|
defaultStepStatuses: {
|
||||||
|
1: 'pending',
|
||||||
|
2: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
getStepDefinitionForState(step) {
|
||||||
|
return {
|
||||||
|
1: { id: 1, key: 'open-chatgpt' },
|
||||||
|
2: { id: 2, key: 'submit-signup-email' },
|
||||||
|
10: { id: 10, key: 'oauth-login' },
|
||||||
|
}[Number(step)] || null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const patch = helpers.buildSessionStatePatch({
|
||||||
|
currentStep: 1,
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'running',
|
||||||
|
2: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
oauthUrl: 'https://old.example.com/start',
|
||||||
|
}, {
|
||||||
|
runtimeState: {
|
||||||
|
activeRunId: 'run-001',
|
||||||
|
flowState: {
|
||||||
|
openai: {
|
||||||
|
auth: {
|
||||||
|
oauthUrl: 'https://new.example.com/start',
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusCheckoutTabId: 99,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legacyStepCompat: {
|
||||||
|
currentStep: 10,
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'completed',
|
||||||
|
10: 'running',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(patch.activeFlowId, 'openai');
|
||||||
|
assert.equal(patch.activeRunId, 'run-001');
|
||||||
|
assert.equal(patch.currentNodeId, 'oauth-login');
|
||||||
|
assert.equal(patch.oauthUrl, 'https://new.example.com/start');
|
||||||
|
assert.equal(patch.plusCheckoutTabId, 99);
|
||||||
|
assert.equal(patch.currentStep, 10);
|
||||||
|
assert.deepStrictEqual(patch.stepStatuses, {
|
||||||
|
1: 'completed',
|
||||||
|
2: 'pending',
|
||||||
|
10: 'running',
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(patch.nodeStatuses, {
|
||||||
|
'open-chatgpt': 'completed',
|
||||||
|
'submit-signup-email': 'pending',
|
||||||
|
'oauth-login': 'running',
|
||||||
|
});
|
||||||
|
assert.equal(patch.runtimeState.flowState.openai.auth.oauthUrl, 'https://new.example.com/start');
|
||||||
|
assert.equal(patch.runtimeState.flowState.openai.plus.plusCheckoutTabId, 99);
|
||||||
|
});
|
||||||
@@ -95,6 +95,51 @@ return {
|
|||||||
assert.equal(api.logs.some((entry) => /固定为邮箱注册/.test(entry.message)), true);
|
assert.equal(api.logs.some((entry) => /固定为邮箱注册/.test(entry.message)), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('signup method resolution respects the shared flow capability registry when available', () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const self = {
|
||||||
|
MultiPageFlowCapabilities: {
|
||||||
|
createFlowCapabilityRegistry() {
|
||||||
|
return {
|
||||||
|
resolveSidepanelCapabilities({ state = {}, signupMethod = 'email' } = {}) {
|
||||||
|
const phoneAllowed = String(state?.activeFlowId || '').trim().toLowerCase() === 'openai';
|
||||||
|
return {
|
||||||
|
canUsePhoneSignup: phoneAllowed,
|
||||||
|
effectiveSignupMethod: signupMethod === 'phone' && phoneAllowed ? 'phone' : 'email',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
|
${extractFunction('normalizeSignupMethod')}
|
||||||
|
${extractFunction('canUsePhoneSignup')}
|
||||||
|
${extractFunction('resolveSignupMethod')}
|
||||||
|
return {
|
||||||
|
canUsePhoneSignup,
|
||||||
|
resolveSignupMethod,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(api.canUsePhoneSignup({
|
||||||
|
activeFlowId: 'site-a',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
signupMethod: 'phone',
|
||||||
|
}), false);
|
||||||
|
assert.equal(api.resolveSignupMethod({
|
||||||
|
activeFlowId: 'site-a',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
signupMethod: 'phone',
|
||||||
|
}), 'email');
|
||||||
|
assert.equal(api.resolveSignupMethod({
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
signupMethod: 'phone',
|
||||||
|
}), 'phone');
|
||||||
|
});
|
||||||
|
|
||||||
test('background step definitions resolve titles from the frozen signup method', () => {
|
test('background step definitions resolve titles from the frozen signup method', () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const captured = [];
|
const captured = [];
|
||||||
|
|||||||
@@ -16,6 +16,76 @@ test('tab runtime module exposes a factory', () => {
|
|||||||
assert.equal(typeof api?.createTabRuntime, 'function');
|
assert.equal(typeof api?.createTabRuntime, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('tab runtime accepts canonical openai-auth readiness for queued signup-page commands', async () => {
|
||||||
|
const runtimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||||
|
const registrySource = fs.readFileSync('shared/source-registry.js', 'utf8');
|
||||||
|
const runtimeApi = new Function('self', `${runtimeSource}; return self.MultiPageBackgroundTabRuntime;`)({});
|
||||||
|
const registryApi = new Function('self', `${registrySource}; return self.MultiPageSourceRegistry;`)({});
|
||||||
|
const sourceRegistry = registryApi.createSourceRegistry();
|
||||||
|
|
||||||
|
const sentMessages = [];
|
||||||
|
let currentState = {
|
||||||
|
tabRegistry: {
|
||||||
|
'signup-page': { tabId: 91, ready: true },
|
||||||
|
},
|
||||||
|
sourceLastUrls: {
|
||||||
|
'signup-page': 'https://auth.openai.com/authorize',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtime = runtimeApi.createTabRuntime({
|
||||||
|
LOG_PREFIX: '[test]',
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
get: async (tabId) => ({
|
||||||
|
id: tabId,
|
||||||
|
windowId: 1,
|
||||||
|
url: 'https://auth.openai.com/authorize',
|
||||||
|
status: 'complete',
|
||||||
|
}),
|
||||||
|
query: async () => [],
|
||||||
|
sendMessage: async (tabId, message) => {
|
||||||
|
if (message.type === 'PING') {
|
||||||
|
return { ok: true, source: 'openai-auth' };
|
||||||
|
}
|
||||||
|
sentMessages.push({ tabId, message });
|
||||||
|
return { ok: true };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getSourceLabel: (source) => source || 'unknown',
|
||||||
|
getState: async () => currentState,
|
||||||
|
matchesSourceUrlFamily: (source, candidateUrl, referenceUrl) => (
|
||||||
|
sourceRegistry.matchesSourceUrlFamily(source, candidateUrl, referenceUrl)
|
||||||
|
),
|
||||||
|
setState: async (updates) => {
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
},
|
||||||
|
sourceRegistry,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(await runtime.getTabId('openai-auth'), 91);
|
||||||
|
|
||||||
|
currentState = {
|
||||||
|
tabRegistry: {},
|
||||||
|
sourceLastUrls: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const queued = runtime.queueCommand('signup-page', { type: 'STEP2_TEST' }, 1000);
|
||||||
|
runtime.flushCommand('openai-auth', 55);
|
||||||
|
await assert.doesNotReject(queued);
|
||||||
|
assert.deepEqual(sentMessages, [{ tabId: 55, message: { type: 'STEP2_TEST' } }]);
|
||||||
|
|
||||||
|
await runtime.ensureContentScriptReadyOnTab('signup-page', 77, {
|
||||||
|
timeoutMs: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(currentState.tabRegistry['openai-auth'], { tabId: 77, ready: true, windowId: 1 });
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(currentState.tabRegistry, 'signup-page'), false);
|
||||||
|
});
|
||||||
|
|
||||||
test('tab runtime caps per-attempt response timeout to the remaining resilient timeout budget', () => {
|
test('tab runtime caps per-attempt response timeout to the remaining resilient timeout budget', () => {
|
||||||
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
|
|||||||
@@ -81,6 +81,38 @@ return { detectScriptSource };
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('detectScriptSource maps OpenAI auth hosts to canonical openai-auth source', () => {
|
||||||
|
const bundle = [extractFunction('detectScriptSource')].join('\n');
|
||||||
|
const api = new Function(`
|
||||||
|
${bundle}
|
||||||
|
return { detectScriptSource };
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
api.detectScriptSource({
|
||||||
|
url: 'https://auth.openai.com/create-account',
|
||||||
|
hostname: 'auth.openai.com',
|
||||||
|
}),
|
||||||
|
'openai-auth'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detectScriptSource returns unknown-source for unrecognized pages', () => {
|
||||||
|
const bundle = [extractFunction('detectScriptSource')].join('\n');
|
||||||
|
const api = new Function(`
|
||||||
|
${bundle}
|
||||||
|
return { detectScriptSource };
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
api.detectScriptSource({
|
||||||
|
url: 'https://example.com/',
|
||||||
|
hostname: 'example.com',
|
||||||
|
}),
|
||||||
|
'unknown-source'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('shouldReportReadyForFrame suppresses noisy plus checkout child frame ready logs', () => {
|
test('shouldReportReadyForFrame suppresses noisy plus checkout child frame ready logs', () => {
|
||||||
const bundle = [extractFunction('shouldReportReadyForFrame')].join('\n');
|
const bundle = [extractFunction('shouldReportReadyForFrame')].join('\n');
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
@@ -91,6 +123,8 @@ return { shouldReportReadyForFrame };
|
|||||||
assert.equal(api.shouldReportReadyForFrame('plus-checkout', true), false);
|
assert.equal(api.shouldReportReadyForFrame('plus-checkout', true), false);
|
||||||
assert.equal(api.shouldReportReadyForFrame('plus-checkout', false), true);
|
assert.equal(api.shouldReportReadyForFrame('plus-checkout', false), true);
|
||||||
assert.equal(api.shouldReportReadyForFrame('paypal-flow', true), true);
|
assert.equal(api.shouldReportReadyForFrame('paypal-flow', true), true);
|
||||||
|
assert.equal(api.shouldReportReadyForFrame('unknown-source', false), true);
|
||||||
|
assert.equal(api.shouldReportReadyForFrame('unknown-source', true), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getRuntimeScriptSource follows injected source overrides after utils is already loaded', () => {
|
test('getRuntimeScriptSource follows injected source overrides after utils is already loaded', () => {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
|
||||||
|
|
||||||
|
function loadApi() {
|
||||||
|
const scope = {};
|
||||||
|
return new Function('self', `${source}; return self.MultiPageFlowCapabilities;`)(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('flow capability registry keeps OpenAI phone signup available only when runtime locks allow it', () => {
|
||||||
|
const api = loadApi();
|
||||||
|
const registry = api.createFlowCapabilityRegistry();
|
||||||
|
|
||||||
|
const enabledState = registry.resolveSidepanelCapabilities({
|
||||||
|
state: {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
panelMode: 'cpa',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
plusModeEnabled: false,
|
||||||
|
contributionMode: false,
|
||||||
|
signupMethod: 'phone',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(enabledState.canUsePhoneSignup, true);
|
||||||
|
assert.equal(enabledState.effectiveSignupMethod, 'phone');
|
||||||
|
assert.equal(enabledState.shouldWarnCpaPhoneSignup, true);
|
||||||
|
assert.deepEqual(enabledState.effectiveSignupMethods, ['email', 'phone']);
|
||||||
|
|
||||||
|
const plusLockedState = registry.resolveSidepanelCapabilities({
|
||||||
|
state: {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
panelMode: 'sub2api',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
contributionMode: false,
|
||||||
|
signupMethod: 'phone',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(plusLockedState.canUsePhoneSignup, false);
|
||||||
|
assert.equal(plusLockedState.effectiveSignupMethod, 'email');
|
||||||
|
assert.equal(plusLockedState.shouldWarnCpaPhoneSignup, false);
|
||||||
|
assert.deepEqual(plusLockedState.effectiveSignupMethods, ['email']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flow capability registry defaults unknown flows to minimal non-phone capabilities', () => {
|
||||||
|
const api = loadApi();
|
||||||
|
const registry = api.createFlowCapabilityRegistry();
|
||||||
|
|
||||||
|
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||||
|
state: {
|
||||||
|
activeFlowId: 'site-a',
|
||||||
|
panelMode: 'codex2api',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
plusModeEnabled: true,
|
||||||
|
contributionMode: true,
|
||||||
|
signupMethod: 'phone',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(capabilityState.activeFlowId, 'site-a');
|
||||||
|
assert.equal(capabilityState.canShowPhoneSettings, false);
|
||||||
|
assert.equal(capabilityState.canShowPlusSettings, false);
|
||||||
|
assert.equal(capabilityState.canShowLuckmail, false);
|
||||||
|
assert.equal(capabilityState.canUsePhoneSignup, false);
|
||||||
|
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||||
|
assert.equal(capabilityState.panelMode, 'codex2api');
|
||||||
|
assert.deepEqual(capabilityState.supportedPanelModes, []);
|
||||||
|
});
|
||||||
@@ -294,6 +294,58 @@ return {
|
|||||||
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
|
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sidepanel phone signup gating can follow the shared flow capability registry', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizeSignupMethod'),
|
||||||
|
extractFunction('normalizePanelMode'),
|
||||||
|
extractFunction('canSelectPhoneSignupMethod'),
|
||||||
|
extractFunction('shouldWarnCpaPhoneSignup'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const window = {
|
||||||
|
MultiPageFlowCapabilities: {
|
||||||
|
createFlowCapabilityRegistry() {
|
||||||
|
return {
|
||||||
|
resolveSidepanelCapabilities({ state = {}, panelMode = 'cpa', signupMethod = 'email' } = {}) {
|
||||||
|
const phoneAllowed = String(state?.activeFlowId || '').trim().toLowerCase() === 'openai';
|
||||||
|
return {
|
||||||
|
canSelectPhoneSignup: phoneAllowed,
|
||||||
|
shouldWarnCpaPhoneSignup: phoneAllowed && signupMethod === 'phone' && panelMode === 'cpa',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let latestState = {
|
||||||
|
activeFlowId: 'site-a',
|
||||||
|
contributionMode: false,
|
||||||
|
panelMode: 'cpa',
|
||||||
|
};
|
||||||
|
const inputPhoneVerificationEnabled = { checked: true };
|
||||||
|
const inputPlusModeEnabled = { checked: false };
|
||||||
|
function getSelectedPanelMode() { return 'cpa'; }
|
||||||
|
function getSelectedSignupMethod() { return 'phone'; }
|
||||||
|
function isCpaPhoneSignupPromptDismissed() { return false; }
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
canSelectPhoneSignupMethod,
|
||||||
|
shouldWarnCpaPhoneSignup,
|
||||||
|
setFlow(flowId) {
|
||||||
|
latestState.activeFlowId = flowId;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(api.canSelectPhoneSignupMethod(), false);
|
||||||
|
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
|
||||||
|
|
||||||
|
api.setFlow('openai');
|
||||||
|
assert.equal(api.canSelectPhoneSignupMethod(), true);
|
||||||
|
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), true);
|
||||||
|
});
|
||||||
|
|
||||||
test('manual step 3 uses phone identity without requiring registration email', () => {
|
test('manual step 3 uses phone identity without requiring registration email', () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111', accountIdentifierType: 'phone', accountIdentifier: '+441111111111' };
|
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111', accountIdentifierType: 'phone', accountIdentifier: '+441111111111' };
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ test('sidepanel signup method UI syncs shared step definitions with the selected
|
|||||||
|
|
||||||
test('sidepanel applies restored signup method when rebuilding shared step definitions on load', () => {
|
test('sidepanel applies restored signup method when rebuilding shared step definitions on load', () => {
|
||||||
const source = extractFunction('applySettingsState');
|
const source = extractFunction('applySettingsState');
|
||||||
assert.match(source, /syncStepDefinitionsForMode\(Boolean\(state\?\.plusModeEnabled\),\s*\{/);
|
assert.match(source, /resolveStepDefinitionCapabilityState\(state/);
|
||||||
assert.match(source, /signupMethod:\s*state\?\.signupMethod/);
|
assert.match(source, /signupMethod:\s*stepDefinitionState\.signupMethod/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', () => {
|
test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', () => {
|
||||||
@@ -165,6 +165,62 @@ return { updatePlusModeUI, selectPlusPaymentMethod, rowPayPalAccount };
|
|||||||
assert.equal(api.rowPayPalAccount.style.display, '');
|
assert.equal(api.rowPayPalAccount.style.display, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sidepanel Plus UI can hide Plus controls when the shared flow capability registry disables them', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizePlusPaymentMethod'),
|
||||||
|
extractFunction('getSelectedPlusPaymentMethod'),
|
||||||
|
extractFunction('normalizeGpcHelperPhoneModeValue'),
|
||||||
|
extractFunction('getGpcHelperAutoModeEnabled'),
|
||||||
|
extractFunction('normalizeGpcAutoModePermissionValue'),
|
||||||
|
extractFunction('getGpcAutoModePermissionFromPayload'),
|
||||||
|
extractFunction('shouldPreserveSelectedGpcAutoMode'),
|
||||||
|
extractFunction('hasGpcAutoModePermissionField'),
|
||||||
|
extractFunction('isGpcAutoModePermissionDenied'),
|
||||||
|
extractFunction('normalizeGpcOtpChannelValue'),
|
||||||
|
extractFunction('updatePlusModeUI'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
const window = {
|
||||||
|
MultiPageFlowCapabilities: {
|
||||||
|
createFlowCapabilityRegistry() {
|
||||||
|
return {
|
||||||
|
resolveSidepanelCapabilities() {
|
||||||
|
return {
|
||||||
|
canShowPlusSettings: false,
|
||||||
|
runtimeLocks: { plusModeEnabled: false },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let latestState = { plusPaymentMethod: 'paypal' };
|
||||||
|
const inputPlusModeEnabled = { checked: true };
|
||||||
|
const rowPlusMode = { style: { display: '' } };
|
||||||
|
const selectPlusPaymentMethod = { value: 'paypal', style: { display: '' } };
|
||||||
|
const rowPlusPaymentMethod = { style: { display: '' } };
|
||||||
|
const rowPayPalAccount = { style: { display: '' } };
|
||||||
|
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
|
||||||
|
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
rowPlusMode,
|
||||||
|
rowPlusPaymentMethod,
|
||||||
|
rowPayPalAccount,
|
||||||
|
selectPlusPaymentMethod,
|
||||||
|
updatePlusModeUI,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
api.updatePlusModeUI();
|
||||||
|
|
||||||
|
assert.equal(api.rowPlusMode.style.display, 'none');
|
||||||
|
assert.equal(api.rowPlusPaymentMethod.style.display, 'none');
|
||||||
|
assert.equal(api.rowPayPalAccount.style.display, 'none');
|
||||||
|
assert.equal(api.selectPlusPaymentMethod.style.display, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
test('sidepanel step definitions keep GPC helper mode distinct', () => {
|
test('sidepanel step definitions keep GPC helper mode distinct', () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizeSignupMethod'),
|
extractFunction('normalizeSignupMethod'),
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
test('background imports shared source registry module', () => {
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
assert.match(source, /shared\/source-registry\.js/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manifest loads shared source registry before content utils in static bundles', () => {
|
||||||
|
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
|
||||||
|
for (const entry of manifest.content_scripts || []) {
|
||||||
|
const scripts = Array.isArray(entry.js) ? entry.js : [];
|
||||||
|
if (!scripts.includes('content/utils.js')) continue;
|
||||||
|
assert.ok(scripts.includes('shared/source-registry.js'));
|
||||||
|
assert.ok(
|
||||||
|
scripts.indexOf('shared/source-registry.js') < scripts.indexOf('content/utils.js'),
|
||||||
|
'shared/source-registry.js must load before content/utils.js'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shared source registry exposes canonical source, alias, detection, and ready policies', () => {
|
||||||
|
const source = fs.readFileSync('shared/source-registry.js', 'utf8');
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageSourceRegistry;`)({});
|
||||||
|
const registry = api.createSourceRegistry();
|
||||||
|
|
||||||
|
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
|
||||||
|
assert.deepEqual(registry.getSourceKeys('signup-page'), ['openai-auth', 'signup-page']);
|
||||||
|
assert.equal(registry.getSourceLabel('openai-auth'), '认证页');
|
||||||
|
assert.equal(
|
||||||
|
registry.matchesSourceUrlFamily(
|
||||||
|
'openai-auth',
|
||||||
|
'https://chatgpt.com/',
|
||||||
|
'https://auth.openai.com/authorize?client_id=test'
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
registry.detectSourceFromLocation({
|
||||||
|
url: 'https://auth.openai.com/create-account',
|
||||||
|
hostname: 'auth.openai.com',
|
||||||
|
}),
|
||||||
|
'openai-auth'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
registry.detectSourceFromLocation({
|
||||||
|
url: 'https://example.com/',
|
||||||
|
hostname: 'example.com',
|
||||||
|
}),
|
||||||
|
'unknown-source'
|
||||||
|
);
|
||||||
|
assert.equal(registry.shouldReportReadyForFrame('mail-163', true), false);
|
||||||
|
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false);
|
||||||
|
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
|
||||||
|
});
|
||||||
@@ -9,6 +9,7 @@ const api = new Function('self', `${source}; return self.MultiPageBackgroundVeri
|
|||||||
function createVerificationFlowTestHelpers(overrides = {}) {
|
function createVerificationFlowTestHelpers(overrides = {}) {
|
||||||
return api.createVerificationFlowHelpers({
|
return api.createVerificationFlowHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
|
buildVerificationPollPayload: null,
|
||||||
chrome: {
|
chrome: {
|
||||||
tabs: {
|
tabs: {
|
||||||
update: async () => {},
|
update: async () => {},
|
||||||
@@ -43,6 +44,42 @@ function createVerificationFlowTestHelpers(overrides = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('verification flow prefers injected verification poll payload builder when provided', () => {
|
||||||
|
const helpers = createVerificationFlowTestHelpers({
|
||||||
|
buildVerificationPollPayload: (step, state, overrides = {}) => ({
|
||||||
|
flowId: state.activeFlowId || 'openai',
|
||||||
|
ruleId: 'custom-rule',
|
||||||
|
step,
|
||||||
|
senderFilters: ['custom-sender'],
|
||||||
|
subjectFilters: ['custom-subject'],
|
||||||
|
targetEmail: state.email,
|
||||||
|
maxAttempts: 9,
|
||||||
|
intervalMs: 4321,
|
||||||
|
...overrides,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
helpers.getVerificationPollPayload(4, {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
email: 'user@example.com',
|
||||||
|
}, {
|
||||||
|
excludeCodes: ['111111'],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
flowId: 'openai',
|
||||||
|
ruleId: 'custom-rule',
|
||||||
|
step: 4,
|
||||||
|
senderFilters: ['custom-sender'],
|
||||||
|
subjectFilters: ['custom-subject'],
|
||||||
|
targetEmail: 'user@example.com',
|
||||||
|
maxAttempts: 9,
|
||||||
|
intervalMs: 4321,
|
||||||
|
excludeCodes: ['111111'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('verification flow keeps 2925 polling cadence in the default payload', () => {
|
test('verification flow keeps 2925 polling cadence in the default payload', () => {
|
||||||
const helpers = api.createVerificationFlowHelpers({
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
|
|||||||
Reference in New Issue
Block a user