feat: 增强步骤 3 的手机号注册逻辑,确保手机号身份优先处理并添加相关测试用例
This commit is contained in:
@@ -245,6 +245,10 @@ const CONTRIBUTION_SOURCE_CPA = 'cpa';
|
|||||||
const CONTRIBUTION_SOURCE_SUB2API = 'sub2api';
|
const CONTRIBUTION_SOURCE_SUB2API = 'sub2api';
|
||||||
const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池';
|
const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池';
|
||||||
const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus';
|
const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus';
|
||||||
|
const DEFAULT_SUB2API_GROUP_NAMES = [
|
||||||
|
DEFAULT_SUB2API_GROUP_NAME,
|
||||||
|
CONTRIBUTION_SUB2API_PLUS_GROUP_NAME,
|
||||||
|
];
|
||||||
const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
|
const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
|
||||||
const DEFAULT_IP_PROXY_SERVICE = '711proxy';
|
const DEFAULT_IP_PROXY_SERVICE = '711proxy';
|
||||||
const IP_PROXY_SERVICE_VALUES = ['711proxy', 'lumiproxy', 'iproyal', 'omegaproxy'];
|
const IP_PROXY_SERVICE_VALUES = ['711proxy', 'lumiproxy', 'iproyal', 'omegaproxy'];
|
||||||
@@ -568,6 +572,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
sub2apiEmail: '',
|
sub2apiEmail: '',
|
||||||
sub2apiPassword: '',
|
sub2apiPassword: '',
|
||||||
sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
|
sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
|
||||||
|
sub2apiGroupNames: DEFAULT_SUB2API_GROUP_NAMES,
|
||||||
sub2apiDefaultProxyName: DEFAULT_SUB2API_PROXY_NAME,
|
sub2apiDefaultProxyName: DEFAULT_SUB2API_PROXY_NAME,
|
||||||
ipProxyEnabled: false,
|
ipProxyEnabled: false,
|
||||||
ipProxyService: DEFAULT_IP_PROXY_SERVICE,
|
ipProxyService: DEFAULT_IP_PROXY_SERVICE,
|
||||||
@@ -2058,6 +2063,24 @@ function resolveCloudflareTempEmailPollTargetEmail(state = {}, pollPayload = {},
|
|||||||
return normalizeCloudflareTempEmailReceiveMailbox(state.email);
|
return normalizeCloudflareTempEmailReceiveMailbox(state.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSub2ApiGroupNames(value = '') {
|
||||||
|
const source = Array.isArray(value)
|
||||||
|
? value
|
||||||
|
: String(value || '').split(/[\r\n,,、]+/);
|
||||||
|
const names = [];
|
||||||
|
const seen = new Set();
|
||||||
|
for (const item of source) {
|
||||||
|
const name = String(item || '').trim();
|
||||||
|
const key = name.toLowerCase();
|
||||||
|
if (!key || seen.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(key);
|
||||||
|
names.push(name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePersistentSettingValue(key, value) {
|
function normalizePersistentSettingValue(key, value) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'panelMode':
|
case 'panelMode':
|
||||||
@@ -2076,6 +2099,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return String(value || '');
|
return String(value || '');
|
||||||
case 'sub2apiGroupName':
|
case 'sub2apiGroupName':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
|
case 'sub2apiGroupNames':
|
||||||
|
return normalizeSub2ApiGroupNames(value);
|
||||||
case 'sub2apiDefaultProxyName':
|
case 'sub2apiDefaultProxyName':
|
||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
case 'ipProxyEnabled':
|
case 'ipProxyEnabled':
|
||||||
@@ -2413,6 +2438,18 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
}
|
}
|
||||||
payload.cloudflareTempEmailDomains = domains;
|
payload.cloudflareTempEmailDomains = domains;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(payload, 'sub2apiGroupName')
|
||||||
|
|| Object.prototype.hasOwnProperty.call(payload, 'sub2apiGroupNames')
|
||||||
|
) {
|
||||||
|
const groupNames = normalizeSub2ApiGroupNames([
|
||||||
|
...(Array.isArray(payload.sub2apiGroupNames) ? payload.sub2apiGroupNames : []),
|
||||||
|
payload.sub2apiGroupName,
|
||||||
|
]);
|
||||||
|
payload.sub2apiGroupNames = groupNames.length
|
||||||
|
? groupNames
|
||||||
|
: [...DEFAULT_SUB2API_GROUP_NAMES];
|
||||||
|
}
|
||||||
const nextSignupConstraintState = {
|
const nextSignupConstraintState = {
|
||||||
...PERSISTED_SETTING_DEFAULTS,
|
...PERSISTED_SETTING_DEFAULTS,
|
||||||
...payload,
|
...payload,
|
||||||
@@ -10137,6 +10174,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
|
|||||||
ensureHotmailAccountForFlow,
|
ensureHotmailAccountForFlow,
|
||||||
ensureMail2925AccountForFlow,
|
ensureMail2925AccountForFlow,
|
||||||
ensureLuckmailPurchaseForFlow,
|
ensureLuckmailPurchaseForFlow,
|
||||||
|
fetchGeneratedEmail,
|
||||||
getTabId,
|
getTabId,
|
||||||
isGeneratedAliasProvider,
|
isGeneratedAliasProvider,
|
||||||
isReusableGeneratedAliasEmail,
|
isReusableGeneratedAliasEmail,
|
||||||
@@ -10157,6 +10195,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
|
|||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
|
setState,
|
||||||
SIGNUP_ENTRY_URL,
|
SIGNUP_ENTRY_URL,
|
||||||
SIGNUP_PAGE_INJECT_FILES,
|
SIGNUP_PAGE_INJECT_FILES,
|
||||||
waitForTabStableComplete,
|
waitForTabStableComplete,
|
||||||
@@ -10256,6 +10295,7 @@ const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({
|
|||||||
generatePassword,
|
generatePassword,
|
||||||
getTabId,
|
getTabId,
|
||||||
isTabAlive,
|
isTabAlive,
|
||||||
|
resolveSignupMethod,
|
||||||
sendToContentScript,
|
sendToContentScript,
|
||||||
setPasswordState,
|
setPasswordState,
|
||||||
setState,
|
setState,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
ensureHotmailAccountForFlow,
|
ensureHotmailAccountForFlow,
|
||||||
ensureMail2925AccountForFlow,
|
ensureMail2925AccountForFlow,
|
||||||
ensureLuckmailPurchaseForFlow,
|
ensureLuckmailPurchaseForFlow,
|
||||||
|
fetchGeneratedEmail,
|
||||||
isGeneratedAliasProvider,
|
isGeneratedAliasProvider,
|
||||||
isReusableGeneratedAliasEmail,
|
isReusableGeneratedAliasEmail,
|
||||||
isHotmailProvider,
|
isHotmailProvider,
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
|
setState,
|
||||||
SIGNUP_ENTRY_URL,
|
SIGNUP_ENTRY_URL,
|
||||||
SIGNUP_PAGE_INJECT_FILES,
|
SIGNUP_PAGE_INJECT_FILES,
|
||||||
waitForTabStableComplete = null,
|
waitForTabStableComplete = null,
|
||||||
@@ -256,8 +258,52 @@
|
|||||||
return result || {};
|
return result || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveSignupEmailForFlow(state) {
|
function getPreservedPhoneIdentityForEmailResolution(state = {}, options = {}) {
|
||||||
|
if (!Boolean(options?.preserveAccountIdentity)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const accountIdentifierType = String(state?.accountIdentifierType || '').trim().toLowerCase();
|
||||||
|
const signupPhoneNumber = String(
|
||||||
|
state?.signupPhoneNumber
|
||||||
|
|| (accountIdentifierType === 'phone' ? state?.accountIdentifier : '')
|
||||||
|
|| state?.signupPhoneCompletedActivation?.phoneNumber
|
||||||
|
|| state?.signupPhoneActivation?.phoneNumber
|
||||||
|
|| ''
|
||||||
|
).trim();
|
||||||
|
if (accountIdentifierType !== 'phone' && !signupPhoneNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: signupPhoneNumber || String(state?.accountIdentifier || '').trim(),
|
||||||
|
signupPhoneNumber,
|
||||||
|
signupPhoneActivation: state?.signupPhoneActivation || null,
|
||||||
|
signupPhoneCompletedActivation: state?.signupPhoneCompletedActivation || null,
|
||||||
|
signupPhoneVerificationRequestedAt: state?.signupPhoneVerificationRequestedAt ?? null,
|
||||||
|
signupPhoneVerificationPurpose: state?.signupPhoneVerificationPurpose || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistResolvedSignupEmail(resolvedEmail, state = {}, options = {}) {
|
||||||
|
if (resolvedEmail === state.email && !options?.preserveAccountIdentity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const preservedPhoneIdentity = getPreservedPhoneIdentityForEmailResolution(state, options);
|
||||||
|
if (preservedPhoneIdentity && typeof setState === 'function') {
|
||||||
|
await setState({
|
||||||
|
email: resolvedEmail,
|
||||||
|
...preservedPhoneIdentity,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resolvedEmail !== state.email) {
|
||||||
|
await setEmailState(resolvedEmail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveSignupEmailForFlow(state, options = {}) {
|
||||||
let resolvedEmail = state.email;
|
let resolvedEmail = state.email;
|
||||||
|
let generatedEmailAlreadyPersisted = false;
|
||||||
if (isHotmailProvider(state)) {
|
if (isHotmailProvider(state)) {
|
||||||
const account = await ensureHotmailAccountForFlow({
|
const account = await ensureHotmailAccountForFlow({
|
||||||
allowAllocate: true,
|
allowAllocate: true,
|
||||||
@@ -281,14 +327,17 @@
|
|||||||
if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) {
|
if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) {
|
||||||
resolvedEmail = buildGeneratedAliasEmail(state);
|
resolvedEmail = buildGeneratedAliasEmail(state);
|
||||||
}
|
}
|
||||||
|
} else if (!resolvedEmail && typeof fetchGeneratedEmail === 'function') {
|
||||||
|
resolvedEmail = await fetchGeneratedEmail(state, options);
|
||||||
|
generatedEmailAlreadyPersisted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resolvedEmail) {
|
if (!resolvedEmail) {
|
||||||
throw new Error('缺少邮箱地址,请先在侧边栏粘贴邮箱。');
|
throw new Error('缺少邮箱地址,请先在侧边栏粘贴邮箱。');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolvedEmail !== state.email) {
|
if (!generatedEmailAlreadyPersisted || options?.preserveAccountIdentity) {
|
||||||
await setEmailState(resolvedEmail);
|
await persistResolvedSignupEmail(resolvedEmail, state, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedEmail;
|
return resolvedEmail;
|
||||||
|
|||||||
@@ -145,7 +145,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const latestState = typeof getState === 'function' ? await getState() : state;
|
const latestState = typeof getState === 'function' ? await getState() : state;
|
||||||
const resolvedEmail = await resolveSignupEmailForFlow(latestState);
|
const resolvedEmail = await resolveSignupEmailForFlow(latestState, {
|
||||||
|
preserveAccountIdentity: true,
|
||||||
|
});
|
||||||
await addLog(`步骤 ${visibleStep}:检测到添加邮箱页,正在添加邮箱 ${resolvedEmail} 并进入邮箱验证码页...`);
|
await addLog(`步骤 ${visibleStep}:检测到添加邮箱页,正在添加邮箱 ${resolvedEmail} 并进入邮箱验证码页...`);
|
||||||
|
|
||||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||||
|
|||||||
@@ -9,22 +9,47 @@
|
|||||||
generatePassword,
|
generatePassword,
|
||||||
getTabId,
|
getTabId,
|
||||||
isTabAlive,
|
isTabAlive,
|
||||||
|
resolveSignupMethod,
|
||||||
sendToContentScript,
|
sendToContentScript,
|
||||||
setPasswordState,
|
setPasswordState,
|
||||||
setState,
|
setState,
|
||||||
SIGNUP_PAGE_INJECT_FILES,
|
SIGNUP_PAGE_INJECT_FILES,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
|
function normalizeSignupMethod(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase() === 'phone'
|
||||||
|
? 'phone'
|
||||||
|
: 'email';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResolvedSignupMethodForStep3(state = {}) {
|
||||||
|
if (typeof resolveSignupMethod === 'function') {
|
||||||
|
return normalizeSignupMethod(resolveSignupMethod(state));
|
||||||
|
}
|
||||||
|
const frozenMethod = String(state?.resolvedSignupMethod || '').trim().toLowerCase();
|
||||||
|
if (frozenMethod === 'phone' || frozenMethod === 'email') {
|
||||||
|
return normalizeSignupMethod(frozenMethod);
|
||||||
|
}
|
||||||
|
return normalizeSignupMethod(state?.signupMethod);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveStep3AccountIdentity(state = {}) {
|
function resolveStep3AccountIdentity(state = {}) {
|
||||||
const resolvedEmail = String(state?.email || '').trim();
|
const resolvedEmail = String(state?.email || '').trim();
|
||||||
|
const rawAccountIdentifierType = String(state?.accountIdentifierType || '').trim().toLowerCase();
|
||||||
const signupPhoneNumber = String(
|
const signupPhoneNumber = String(
|
||||||
state?.signupPhoneNumber
|
state?.signupPhoneNumber
|
||||||
|| (state?.accountIdentifierType === 'phone' ? state?.accountIdentifier : '')
|
|| (rawAccountIdentifierType === 'phone' ? state?.accountIdentifier : '')
|
||||||
|| ''
|
|| ''
|
||||||
).trim();
|
).trim();
|
||||||
const accountIdentifierType = signupPhoneNumber && state?.accountIdentifierType === 'phone'
|
const explicitEmailIdentity = rawAccountIdentifierType === 'email' && resolvedEmail;
|
||||||
|
const shouldUsePhoneIdentity = !explicitEmailIdentity && (
|
||||||
|
rawAccountIdentifierType === 'phone'
|
||||||
|
|| Boolean(signupPhoneNumber)
|
||||||
|
|| getResolvedSignupMethodForStep3(state) === 'phone'
|
||||||
|
);
|
||||||
|
const accountIdentifierType = shouldUsePhoneIdentity
|
||||||
? 'phone'
|
? 'phone'
|
||||||
: (resolvedEmail ? 'email' : (signupPhoneNumber ? 'phone' : 'email'));
|
: (resolvedEmail ? 'email' : 'email');
|
||||||
const accountIdentifier = accountIdentifierType === 'phone'
|
const accountIdentifier = accountIdentifierType === 'phone'
|
||||||
? signupPhoneNumber
|
? signupPhoneNumber
|
||||||
: resolvedEmail;
|
: resolvedEmail;
|
||||||
@@ -40,6 +65,9 @@
|
|||||||
async function executeStep3(state) {
|
async function executeStep3(state) {
|
||||||
const identity = resolveStep3AccountIdentity(state);
|
const identity = resolveStep3AccountIdentity(state);
|
||||||
if (!identity.accountIdentifier) {
|
if (!identity.accountIdentifier) {
|
||||||
|
if (identity.accountIdentifierType === 'phone') {
|
||||||
|
throw new Error('缺少注册手机号,请先完成步骤 2 或在侧栏填写注册手机号后再执行步骤 3。');
|
||||||
|
}
|
||||||
throw new Error('缺少注册账号,请先完成步骤 2。');
|
throw new Error('缺少注册账号,请先完成步骤 2。');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1971,6 +1971,102 @@ header {
|
|||||||
.data-select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
|
.data-select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
|
||||||
[data-theme="dark"] .data-select { color-scheme: dark; }
|
[data-theme="dark"] .data-select { color-scheme: dark; }
|
||||||
|
|
||||||
|
.sub2api-group-picker {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-trigger {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-caret {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-right: 1.5px solid currentColor;
|
||||||
|
border-bottom: 1.5px solid currentColor;
|
||||||
|
transform: translateY(-2px) rotate(45deg);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-trigger.is-open .sub2api-group-caret {
|
||||||
|
transform: translateY(2px) rotate(225deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 70;
|
||||||
|
top: calc(100% + 4px);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg-base);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-option-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-option,
|
||||||
|
.sub2api-group-delete {
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--transition), color var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-option {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: left;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-option[aria-selected="true"] {
|
||||||
|
color: var(--blue);
|
||||||
|
background: var(--blue-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-option:hover,
|
||||||
|
.sub2api-group-delete:hover:not(:disabled) {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-delete {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 5px 7px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub2api-group-delete:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.42;
|
||||||
|
}
|
||||||
|
|
||||||
.data-check {
|
.data-check {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|||||||
@@ -197,7 +197,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-sub2api-group" style="display:none;">
|
<div class="data-row" id="row-sub2api-group" style="display:none;">
|
||||||
<span class="data-label">分组</span>
|
<span class="data-label">分组</span>
|
||||||
<input type="text" id="input-sub2api-group" class="data-input" placeholder="默认 codex;多个用逗号或换行分隔" />
|
<div class="data-inline">
|
||||||
|
<input type="hidden" id="input-sub2api-group" value="codex" />
|
||||||
|
<div id="sub2api-group-picker" class="sub2api-group-picker">
|
||||||
|
<button id="btn-sub2api-group-menu" class="data-select sub2api-group-trigger" type="button"
|
||||||
|
aria-haspopup="listbox" aria-expanded="false">
|
||||||
|
<span id="sub2api-group-current" class="truncate">codex</span>
|
||||||
|
<span class="sub2api-group-caret" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<div id="sub2api-group-menu" class="sub2api-group-menu" role="listbox" hidden></div>
|
||||||
|
</div>
|
||||||
|
<button id="btn-add-sub2api-group" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-sub2api-default-proxy" style="display:none;">
|
<div class="data-row" id="row-sub2api-default-proxy" style="display:none;">
|
||||||
<span class="data-label">默认代理</span>
|
<span class="data-label">默认代理</span>
|
||||||
|
|||||||
+309
-8
@@ -95,6 +95,11 @@ const rowSub2ApiPassword = document.getElementById('row-sub2api-password');
|
|||||||
const inputSub2ApiPassword = document.getElementById('input-sub2api-password');
|
const inputSub2ApiPassword = document.getElementById('input-sub2api-password');
|
||||||
const rowSub2ApiGroup = document.getElementById('row-sub2api-group');
|
const rowSub2ApiGroup = document.getElementById('row-sub2api-group');
|
||||||
const inputSub2ApiGroup = document.getElementById('input-sub2api-group');
|
const inputSub2ApiGroup = document.getElementById('input-sub2api-group');
|
||||||
|
const sub2ApiGroupPicker = document.getElementById('sub2api-group-picker');
|
||||||
|
const btnSub2ApiGroupMenu = document.getElementById('btn-sub2api-group-menu');
|
||||||
|
const sub2ApiGroupCurrent = document.getElementById('sub2api-group-current');
|
||||||
|
const sub2ApiGroupMenu = document.getElementById('sub2api-group-menu');
|
||||||
|
const btnAddSub2ApiGroup = document.getElementById('btn-add-sub2api-group');
|
||||||
const rowSub2ApiDefaultProxy = document.getElementById('row-sub2api-default-proxy');
|
const rowSub2ApiDefaultProxy = document.getElementById('row-sub2api-default-proxy');
|
||||||
const inputSub2ApiDefaultProxy = document.getElementById('input-sub2api-default-proxy');
|
const inputSub2ApiDefaultProxy = document.getElementById('input-sub2api-default-proxy');
|
||||||
const rowIpProxyEnabled = document.getElementById('row-ip-proxy-enabled');
|
const rowIpProxyEnabled = document.getElementById('row-ip-proxy-enabled');
|
||||||
@@ -1092,6 +1097,138 @@ let configActionInFlight = false;
|
|||||||
let currentReleaseSnapshot = null;
|
let currentReleaseSnapshot = null;
|
||||||
let currentContributionContentSnapshot = null;
|
let currentContributionContentSnapshot = null;
|
||||||
let contributionContentSnapshotRequestInFlight = null;
|
let contributionContentSnapshotRequestInFlight = null;
|
||||||
|
|
||||||
|
const DEFAULT_SUB2API_GROUP_OPTIONS = ['codex', 'openai-plus'];
|
||||||
|
let sub2ApiGroupMenuOpen = false;
|
||||||
|
|
||||||
|
function splitSub2ApiGroupNames(value = '') {
|
||||||
|
return String(value || '')
|
||||||
|
.split(/[\r\n,,、]+/)
|
||||||
|
.map((name) => name.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSub2ApiGroupOptions(...sources) {
|
||||||
|
const groups = [];
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
const append = (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(append);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const name of splitSub2ApiGroupNames(value)) {
|
||||||
|
const key = name.toLowerCase();
|
||||||
|
if (!key || seen.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(key);
|
||||||
|
groups.push(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sources.forEach(append);
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedSub2ApiGroupName() {
|
||||||
|
return String(inputSub2ApiGroup?.value || '').trim()
|
||||||
|
|| DEFAULT_SUB2API_GROUP_OPTIONS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSub2ApiGroupOptionsState(state = latestState) {
|
||||||
|
const options = normalizeSub2ApiGroupOptions(
|
||||||
|
state?.sub2apiGroupNames,
|
||||||
|
state?.sub2apiGroupName
|
||||||
|
);
|
||||||
|
return options.length ? options : [...DEFAULT_SUB2API_GROUP_OPTIONS];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSub2ApiGroupMenuOpen(open) {
|
||||||
|
sub2ApiGroupMenuOpen = Boolean(open);
|
||||||
|
if (sub2ApiGroupMenu) {
|
||||||
|
sub2ApiGroupMenu.hidden = !sub2ApiGroupMenuOpen;
|
||||||
|
}
|
||||||
|
if (btnSub2ApiGroupMenu) {
|
||||||
|
btnSub2ApiGroupMenu.setAttribute('aria-expanded', sub2ApiGroupMenuOpen ? 'true' : 'false');
|
||||||
|
btnSub2ApiGroupMenu.classList?.toggle('is-open', sub2ApiGroupMenuOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSub2ApiGroupSelection(groupName, options = {}) {
|
||||||
|
const selected = String(groupName || '').trim() || DEFAULT_SUB2API_GROUP_OPTIONS[0];
|
||||||
|
if (inputSub2ApiGroup) {
|
||||||
|
inputSub2ApiGroup.value = selected;
|
||||||
|
if (options.emit && typeof inputSub2ApiGroup.dispatchEvent === 'function') {
|
||||||
|
inputSub2ApiGroup.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sub2ApiGroupCurrent) {
|
||||||
|
sub2ApiGroupCurrent.textContent = selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSub2ApiGroupOptions(state = latestState, selectedValue = '') {
|
||||||
|
if (!inputSub2ApiGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = String(selectedValue || state?.sub2apiGroupName || '').trim();
|
||||||
|
const options = getSub2ApiGroupOptionsState({
|
||||||
|
...(state || {}),
|
||||||
|
sub2apiGroupName: selected || state?.sub2apiGroupName,
|
||||||
|
});
|
||||||
|
if (selected && !options.some((name) => name.toLowerCase() === selected.toLowerCase())) {
|
||||||
|
options.unshift(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!sub2ApiGroupMenu
|
||||||
|
|| typeof sub2ApiGroupMenu.appendChild !== 'function'
|
||||||
|
|| typeof document === 'undefined'
|
||||||
|
|| typeof document.createElement !== 'function'
|
||||||
|
) {
|
||||||
|
setSub2ApiGroupSelection(selected || options[0] || DEFAULT_SUB2API_GROUP_OPTIONS[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub2ApiGroupMenu.innerHTML = '';
|
||||||
|
options.forEach((name) => {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'sub2api-group-option-row';
|
||||||
|
|
||||||
|
const option = document.createElement('button');
|
||||||
|
option.type = 'button';
|
||||||
|
option.className = 'sub2api-group-option';
|
||||||
|
option.setAttribute('role', 'option');
|
||||||
|
option.setAttribute('aria-selected', name === selected ? 'true' : 'false');
|
||||||
|
option.textContent = name;
|
||||||
|
option.addEventListener('click', () => {
|
||||||
|
setSub2ApiGroupSelection(name, { emit: true });
|
||||||
|
setSub2ApiGroupMenuOpen(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteButton = document.createElement('button');
|
||||||
|
deleteButton.type = 'button';
|
||||||
|
deleteButton.className = 'sub2api-group-delete';
|
||||||
|
deleteButton.textContent = '删除';
|
||||||
|
deleteButton.title = `删除分组 ${name}`;
|
||||||
|
deleteButton.setAttribute('aria-label', `删除分组 ${name}`);
|
||||||
|
deleteButton.disabled = options.length <= 1;
|
||||||
|
deleteButton.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleDeleteSub2ApiGroup(name).catch((error) => {
|
||||||
|
showToast(error?.message || '删除 SUB2API 分组失败。', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
row.appendChild(option);
|
||||||
|
row.appendChild(deleteButton);
|
||||||
|
sub2ApiGroupMenu.appendChild(row);
|
||||||
|
});
|
||||||
|
setSub2ApiGroupSelection(selected || options[0] || DEFAULT_SUB2API_GROUP_OPTIONS[0]);
|
||||||
|
}
|
||||||
let customEmailPoolEntriesState = [];
|
let customEmailPoolEntriesState = [];
|
||||||
|
|
||||||
const EYE_OPEN_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>';
|
const EYE_OPEN_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>';
|
||||||
@@ -3118,6 +3255,35 @@ function collectSettingsPayload() {
|
|||||||
: (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'))
|
: (String(latestState?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'))
|
||||||
);
|
);
|
||||||
const plusPaymentMethod = getSelectedPlusPaymentMethod();
|
const plusPaymentMethod = getSelectedPlusPaymentMethod();
|
||||||
|
const selectedSub2ApiGroupName = String(inputSub2ApiGroup.value || '').trim();
|
||||||
|
const sub2apiGroupNames = [];
|
||||||
|
const seenSub2ApiGroupNames = new Set();
|
||||||
|
const appendSub2ApiGroupNames = (value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(appendSub2ApiGroupNames);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String(value || '')
|
||||||
|
.split(/[\r\n,,、]+/)
|
||||||
|
.map((name) => name.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((name) => {
|
||||||
|
const key = name.toLowerCase();
|
||||||
|
if (!key || seenSub2ApiGroupNames.has(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seenSub2ApiGroupNames.add(key);
|
||||||
|
sub2apiGroupNames.push(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
[
|
||||||
|
latestState?.sub2apiGroupNames,
|
||||||
|
latestState?.sub2apiGroupName,
|
||||||
|
selectedSub2ApiGroupName,
|
||||||
|
].forEach(appendSub2ApiGroupNames);
|
||||||
|
if (sub2apiGroupNames.length === 0) {
|
||||||
|
appendSub2ApiGroupNames(['codex', 'openai-plus']);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...(contributionModeEnabled ? {} : {
|
...(contributionModeEnabled ? {} : {
|
||||||
panelMode: selectPanelMode.value,
|
panelMode: selectPanelMode.value,
|
||||||
@@ -3128,7 +3294,8 @@ function collectSettingsPayload() {
|
|||||||
sub2apiUrl: inputSub2ApiUrl.value.trim(),
|
sub2apiUrl: inputSub2ApiUrl.value.trim(),
|
||||||
sub2apiEmail: inputSub2ApiEmail.value.trim(),
|
sub2apiEmail: inputSub2ApiEmail.value.trim(),
|
||||||
sub2apiPassword: inputSub2ApiPassword.value,
|
sub2apiPassword: inputSub2ApiPassword.value,
|
||||||
sub2apiGroupName: inputSub2ApiGroup.value.trim(),
|
sub2apiGroupName: selectedSub2ApiGroupName,
|
||||||
|
sub2apiGroupNames,
|
||||||
sub2apiDefaultProxyName: inputSub2ApiDefaultProxy.value.trim(),
|
sub2apiDefaultProxyName: inputSub2ApiDefaultProxy.value.trim(),
|
||||||
ipProxyEnabled: getSelectedIpProxyEnabledSafe(),
|
ipProxyEnabled: getSelectedIpProxyEnabledSafe(),
|
||||||
ipProxyService: selectedIpProxyService,
|
ipProxyService: selectedIpProxyService,
|
||||||
@@ -7015,6 +7182,18 @@ function getRuntimeSignupPhoneValue(state = latestState) {
|
|||||||
).trim();
|
).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldExecuteStep3WithSignupPhoneIdentity(state = latestState) {
|
||||||
|
const identifierType = String(state?.accountIdentifierType || '').trim().toLowerCase();
|
||||||
|
const resolvedMethod = normalizeSignupMethod(
|
||||||
|
state?.resolvedSignupMethod
|
||||||
|
|| state?.signupMethod
|
||||||
|
|| (typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : DEFAULT_SIGNUP_METHOD)
|
||||||
|
);
|
||||||
|
return identifierType === 'phone'
|
||||||
|
|| Boolean(getRuntimeSignupPhoneValue(state))
|
||||||
|
|| resolvedMethod === SIGNUP_METHOD_PHONE;
|
||||||
|
}
|
||||||
|
|
||||||
function getSignupPhoneInputValue() {
|
function getSignupPhoneInputValue() {
|
||||||
return typeof inputSignupPhone !== 'undefined' && inputSignupPhone
|
return typeof inputSignupPhone !== 'undefined' && inputSignupPhone
|
||||||
? String(inputSignupPhone.value || '').trim()
|
? String(inputSignupPhone.value || '').trim()
|
||||||
@@ -7715,7 +7894,7 @@ function applySettingsState(state) {
|
|||||||
inputSub2ApiUrl.value = state?.sub2apiUrl || '';
|
inputSub2ApiUrl.value = state?.sub2apiUrl || '';
|
||||||
inputSub2ApiEmail.value = state?.sub2apiEmail || '';
|
inputSub2ApiEmail.value = state?.sub2apiEmail || '';
|
||||||
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
||||||
inputSub2ApiGroup.value = state?.sub2apiGroupName || '';
|
renderSub2ApiGroupOptions(state, state?.sub2apiGroupName || '');
|
||||||
inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || '';
|
inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || '';
|
||||||
const normalizedIpProxyService = resolveIpProxyService(state?.ipProxyService);
|
const normalizedIpProxyService = resolveIpProxyService(state?.ipProxyService);
|
||||||
const normalizedIpProxyServiceProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
const normalizedIpProxyServiceProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
||||||
@@ -9336,6 +9515,90 @@ async function saveCloudflareTempEmailDomainSettings(domains, activeDomain, opti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAddSub2ApiGroup() {
|
||||||
|
if (!sharedFormDialog?.open) {
|
||||||
|
showToast('表单弹窗未加载,请刷新扩展后重试。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await sharedFormDialog.open({
|
||||||
|
title: '添加 SUB2API 分组',
|
||||||
|
confirmLabel: '添加',
|
||||||
|
confirmVariant: 'btn-primary',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'groupName',
|
||||||
|
label: '分组',
|
||||||
|
type: 'text',
|
||||||
|
placeholder: '例如 openai-plus',
|
||||||
|
autocomplete: 'off',
|
||||||
|
required: true,
|
||||||
|
requiredMessage: '请先填写 SUB2API 分组名称。',
|
||||||
|
validate: (value) => {
|
||||||
|
const names = normalizeSub2ApiGroupOptions(value);
|
||||||
|
return names.length ? '' : '请先填写 SUB2API 分组名称。';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGroups = normalizeSub2ApiGroupOptions(result.groupName);
|
||||||
|
if (!newGroups.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedGroup = newGroups[0];
|
||||||
|
const nextGroups = normalizeSub2ApiGroupOptions(
|
||||||
|
getSub2ApiGroupOptionsState(latestState),
|
||||||
|
newGroups
|
||||||
|
);
|
||||||
|
syncLatestState({
|
||||||
|
sub2apiGroupName: selectedGroup,
|
||||||
|
sub2apiGroupNames: nextGroups,
|
||||||
|
});
|
||||||
|
renderSub2ApiGroupOptions(latestState, selectedGroup);
|
||||||
|
markSettingsDirty(true);
|
||||||
|
await saveSettings({ silent: true }).catch(() => { });
|
||||||
|
showToast(`已添加并切换到 SUB2API 分组:${selectedGroup}`, 'success', 1800);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteSub2ApiGroup(groupName) {
|
||||||
|
const targetName = String(groupName || '').trim();
|
||||||
|
if (!targetName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentGroups = getSub2ApiGroupOptionsState(latestState);
|
||||||
|
if (currentGroups.length <= 1) {
|
||||||
|
showToast('至少保留一个 SUB2API 分组。', 'warn', 1800);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetKey = targetName.toLowerCase();
|
||||||
|
const nextGroups = currentGroups.filter((name) => name.toLowerCase() !== targetKey);
|
||||||
|
if (nextGroups.length === currentGroups.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentGroup = getSelectedSub2ApiGroupName();
|
||||||
|
const nextSelectedGroup = currentGroup.toLowerCase() === targetKey
|
||||||
|
? nextGroups[0]
|
||||||
|
: (nextGroups.find((name) => name.toLowerCase() === currentGroup.toLowerCase()) || nextGroups[0]);
|
||||||
|
|
||||||
|
syncLatestState({
|
||||||
|
sub2apiGroupName: nextSelectedGroup,
|
||||||
|
sub2apiGroupNames: nextGroups,
|
||||||
|
});
|
||||||
|
renderSub2ApiGroupOptions(latestState, nextSelectedGroup);
|
||||||
|
setSub2ApiGroupMenuOpen(true);
|
||||||
|
markSettingsDirty(true);
|
||||||
|
await saveSettings({ silent: true }).catch(() => { });
|
||||||
|
showToast(`已删除 SUB2API 分组:${targetName}`, 'success', 1600);
|
||||||
|
}
|
||||||
|
|
||||||
function updatePanelModeUI() {
|
function updatePanelModeUI() {
|
||||||
const useSub2Api = selectPanelMode.value === 'sub2api';
|
const useSub2Api = selectPanelMode.value === 'sub2api';
|
||||||
const useCodex2Api = selectPanelMode.value === 'codex2api';
|
const useCodex2Api = selectPanelMode.value === 'codex2api';
|
||||||
@@ -10338,8 +10601,12 @@ stepsList?.addEventListener('click', async (event) => {
|
|||||||
});
|
});
|
||||||
syncLatestState({ customPassword: inputPassword.value });
|
syncLatestState({ customPassword: inputPassword.value });
|
||||||
}
|
}
|
||||||
let email = inputEmail.value.trim();
|
if (shouldExecuteStep3WithSignupPhoneIdentity(latestState)) {
|
||||||
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
|
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
} else if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
|
||||||
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -11412,13 +11679,34 @@ inputSub2ApiPassword.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
inputSub2ApiGroup.addEventListener('input', () => {
|
inputSub2ApiGroup.addEventListener('change', () => {
|
||||||
|
syncLatestState({
|
||||||
|
sub2apiGroupName: getSelectedSub2ApiGroupName(),
|
||||||
|
sub2apiGroupNames: normalizeSub2ApiGroupOptions(
|
||||||
|
getSub2ApiGroupOptionsState(latestState),
|
||||||
|
getSelectedSub2ApiGroupName()
|
||||||
|
),
|
||||||
|
});
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
scheduleSettingsAutoSave();
|
|
||||||
});
|
|
||||||
inputSub2ApiGroup.addEventListener('blur', () => {
|
|
||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
btnSub2ApiGroupMenu?.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setSub2ApiGroupMenuOpen(!sub2ApiGroupMenuOpen);
|
||||||
|
});
|
||||||
|
btnSub2ApiGroupMenu?.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
setSub2ApiGroupMenuOpen(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sub2ApiGroupMenu?.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
btnAddSub2ApiGroup?.addEventListener('click', () => {
|
||||||
|
handleAddSub2ApiGroup().catch((error) => {
|
||||||
|
showToast(error?.message || '添加 SUB2API 分组失败。', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
inputSub2ApiDefaultProxy.addEventListener('input', () => {
|
inputSub2ApiDefaultProxy.addEventListener('input', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
@@ -12570,6 +12858,12 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
selectPanelMode.value = message.payload.panelMode || 'cpa';
|
selectPanelMode.value = message.payload.panelMode || 'cpa';
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
message.payload.sub2apiGroupName !== undefined
|
||||||
|
|| message.payload.sub2apiGroupNames !== undefined
|
||||||
|
) {
|
||||||
|
renderSub2ApiGroupOptions(latestState, latestState?.sub2apiGroupName || '');
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
message.payload.ipProxyEnabled !== undefined
|
message.payload.ipProxyEnabled !== undefined
|
||||||
|| message.payload.ipProxyService !== undefined
|
|| message.payload.ipProxyService !== undefined
|
||||||
@@ -13171,6 +13465,7 @@ document.addEventListener('click', (event) => {
|
|||||||
const clickedInsideFiveSimCountryMenu = Boolean(fiveSimCountryMenuShell?.contains(event.target));
|
const clickedInsideFiveSimCountryMenu = Boolean(fiveSimCountryMenuShell?.contains(event.target));
|
||||||
const clickedInsideNexSmsCountryMenu = Boolean(nexSmsCountryMenuShell?.contains(event.target));
|
const clickedInsideNexSmsCountryMenu = Boolean(nexSmsCountryMenuShell?.contains(event.target));
|
||||||
const clickedInsideProviderOrderMenu = Boolean(phoneSmsProviderOrderMenuShell?.contains(event.target));
|
const clickedInsideProviderOrderMenu = Boolean(phoneSmsProviderOrderMenuShell?.contains(event.target));
|
||||||
|
const clickedInsideSub2ApiGroupPicker = Boolean(sub2ApiGroupPicker?.contains(event.target));
|
||||||
|
|
||||||
if (configMenuOpen && !clickedInsideConfigMenu) {
|
if (configMenuOpen && !clickedInsideConfigMenu) {
|
||||||
closeConfigMenu();
|
closeConfigMenu();
|
||||||
@@ -13192,6 +13487,9 @@ document.addEventListener('click', (event) => {
|
|||||||
if (providerOrderMenuOpen && !clickedInsideProviderOrderMenu) {
|
if (providerOrderMenuOpen && !clickedInsideProviderOrderMenu) {
|
||||||
setPhoneSmsProviderOrderMenuOpen(false);
|
setPhoneSmsProviderOrderMenuOpen(false);
|
||||||
}
|
}
|
||||||
|
if (sub2ApiGroupMenuOpen && !clickedInsideSub2ApiGroupPicker) {
|
||||||
|
setSub2ApiGroupMenuOpen(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
@@ -13213,6 +13511,9 @@ document.addEventListener('keydown', (event) => {
|
|||||||
if (btnPhoneSmsProviderOrderMenu?.getAttribute('aria-expanded') === 'true') {
|
if (btnPhoneSmsProviderOrderMenu?.getAttribute('aria-expanded') === 'true') {
|
||||||
setPhoneSmsProviderOrderMenuOpen(false);
|
setPhoneSmsProviderOrderMenuOpen(false);
|
||||||
}
|
}
|
||||||
|
if (sub2ApiGroupMenuOpen) {
|
||||||
|
setSub2ApiGroupMenuOpen(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ test('background account history settings are normalized independently from hotm
|
|||||||
extractFunction('normalizeFiveSimOperator'),
|
extractFunction('normalizeFiveSimOperator'),
|
||||||
extractFunction('normalizeFiveSimMaxPrice'),
|
extractFunction('normalizeFiveSimMaxPrice'),
|
||||||
extractFunction('normalizeFiveSimCountryFallback'),
|
extractFunction('normalizeFiveSimCountryFallback'),
|
||||||
|
extractFunction('normalizeSub2ApiGroupNames'),
|
||||||
extractFunction('normalizePersistentSettingValue'),
|
extractFunction('normalizePersistentSettingValue'),
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
@@ -247,6 +248,10 @@ return {
|
|||||||
api.normalizePersistentSettingValue('sub2apiDefaultProxyName', ' proxy-a '),
|
api.normalizePersistentSettingValue('sub2apiDefaultProxyName', ' proxy-a '),
|
||||||
'proxy-a'
|
'proxy-a'
|
||||||
);
|
);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
api.normalizePersistentSettingValue('sub2apiGroupNames', [' codex ', 'openai-plus', 'CODEX']),
|
||||||
|
['codex', 'openai-plus']
|
||||||
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
api.normalizePersistentSettingValue('codex2apiUrl', 'localhost:8080/admin'),
|
api.normalizePersistentSettingValue('codex2apiUrl', 'localhost:8080/admin'),
|
||||||
'http://localhost:8080/admin/accounts'
|
'http://localhost:8080/admin/accounts'
|
||||||
|
|||||||
@@ -512,6 +512,73 @@ test('signup flow helper reuses existing managed alias email when it is still co
|
|||||||
assert.equal(setEmailCalls, 0);
|
assert.equal(setEmailCalls, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('signup flow helper can generate an email on demand when add-email starts from phone signup', async () => {
|
||||||
|
const fetchedStates = [];
|
||||||
|
const setStateCalls = [];
|
||||||
|
|
||||||
|
const helpers = signupFlowApi.createSignupFlowHelpers({
|
||||||
|
buildGeneratedAliasEmail: () => '',
|
||||||
|
chrome: { tabs: { get: async () => ({ id: 21, url: 'https://auth.openai.com/create-account/password' }) } },
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
ensureHotmailAccountForFlow: async () => ({}),
|
||||||
|
ensureLuckmailPurchaseForFlow: async () => ({}),
|
||||||
|
fetchGeneratedEmail: async (state, options) => {
|
||||||
|
fetchedStates.push({ state, options });
|
||||||
|
return 'duck.generated@example.com';
|
||||||
|
},
|
||||||
|
isGeneratedAliasProvider: () => false,
|
||||||
|
isReusableGeneratedAliasEmail: () => false,
|
||||||
|
isHotmailProvider: () => false,
|
||||||
|
isLuckmailProvider: () => false,
|
||||||
|
isSignupEmailVerificationPageUrl: () => false,
|
||||||
|
isSignupPasswordPageUrl: () => true,
|
||||||
|
reuseOrCreateTab: async () => 21,
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
setEmailState: async () => {
|
||||||
|
throw new Error('fetchGeneratedEmail already persists the generated email');
|
||||||
|
},
|
||||||
|
setState: async (updates) => {
|
||||||
|
setStateCalls.push(updates);
|
||||||
|
},
|
||||||
|
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
waitForTabUrlMatch: async () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await helpers.resolveSignupEmailForFlow({
|
||||||
|
email: '',
|
||||||
|
emailGenerator: 'duck',
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+447780579093',
|
||||||
|
signupPhoneNumber: '+447780579093',
|
||||||
|
signupPhoneCompletedActivation: {
|
||||||
|
activationId: 'signup-completed',
|
||||||
|
phoneNumber: '+447780579093',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
preserveAccountIdentity: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(email, 'duck.generated@example.com');
|
||||||
|
assert.equal(fetchedStates.length, 1);
|
||||||
|
assert.equal(fetchedStates[0].options.preserveAccountIdentity, true);
|
||||||
|
assert.deepStrictEqual(setStateCalls, [
|
||||||
|
{
|
||||||
|
email: 'duck.generated@example.com',
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+447780579093',
|
||||||
|
signupPhoneNumber: '+447780579093',
|
||||||
|
signupPhoneActivation: null,
|
||||||
|
signupPhoneCompletedActivation: {
|
||||||
|
activationId: 'signup-completed',
|
||||||
|
phoneNumber: '+447780579093',
|
||||||
|
},
|
||||||
|
signupPhoneVerificationRequestedAt: null,
|
||||||
|
signupPhoneVerificationPurpose: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('signup flow helper finalizes step 3 submit by reusing signup verification preparation', async () => {
|
test('signup flow helper finalizes step 3 submit by reusing signup verification preparation', async () => {
|
||||||
let ensureCalls = 0;
|
let ensureCalls = 0;
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
|||||||
@@ -122,3 +122,81 @@ test('step 3 supports phone-only signup identity when password page is present',
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 3 phone signup intent does not fall back to a stale email identity', async () => {
|
||||||
|
const events = {
|
||||||
|
passwordStates: [],
|
||||||
|
messages: [],
|
||||||
|
stateUpdates: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep3Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
generatePassword: () => 'Generated123!',
|
||||||
|
getTabId: async () => 88,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
resolveSignupMethod: () => 'phone',
|
||||||
|
sendToContentScript: async (_source, message) => {
|
||||||
|
events.messages.push(message);
|
||||||
|
},
|
||||||
|
setPasswordState: async (password) => {
|
||||||
|
events.passwordStates.push(password);
|
||||||
|
},
|
||||||
|
setState: async (updates) => {
|
||||||
|
events.stateUpdates.push(updates);
|
||||||
|
},
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executeStep3({
|
||||||
|
email: 'stale@example.com',
|
||||||
|
signupMethod: 'phone',
|
||||||
|
resolvedSignupMethod: 'phone',
|
||||||
|
accountIdentifierType: null,
|
||||||
|
accountIdentifier: '',
|
||||||
|
customPassword: 'PhoneSecret123!',
|
||||||
|
accounts: [],
|
||||||
|
}),
|
||||||
|
/缺少注册手机号,请先完成步骤 2 或在侧栏填写注册手机号后再执行步骤 3。/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.passwordStates, []);
|
||||||
|
assert.deepStrictEqual(events.stateUpdates, []);
|
||||||
|
assert.deepStrictEqual(events.messages, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('step 3 respects resolved email fallback when phone signup is unavailable', async () => {
|
||||||
|
const events = {
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep3Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
generatePassword: () => 'Generated123!',
|
||||||
|
getTabId: async () => 88,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
resolveSignupMethod: () => 'email',
|
||||||
|
sendToContentScript: async (_source, message) => {
|
||||||
|
events.messages.push(message);
|
||||||
|
},
|
||||||
|
setPasswordState: async () => {},
|
||||||
|
setState: async () => {},
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep3({
|
||||||
|
email: 'fallback@example.com',
|
||||||
|
signupMethod: 'phone',
|
||||||
|
resolvedSignupMethod: 'email',
|
||||||
|
customPassword: 'EmailSecret123!',
|
||||||
|
accounts: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(events.messages[0].payload.accountIdentifierType, 'email');
|
||||||
|
assert.equal(events.messages[0].payload.accountIdentifier, 'fallback@example.com');
|
||||||
|
});
|
||||||
|
|||||||
@@ -300,8 +300,9 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
|||||||
isTabAlive: async () => true,
|
isTabAlive: async () => true,
|
||||||
isVerificationMailPollingError: () => false,
|
isVerificationMailPollingError: () => false,
|
||||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
resolveSignupEmailForFlow: async (state) => {
|
resolveSignupEmailForFlow: async (state, options = {}) => {
|
||||||
calls.resolvedStates.push(state);
|
calls.resolvedStates.push(state);
|
||||||
|
calls.resolveOptions = options;
|
||||||
return 'new.user@example.com';
|
return 'new.user@example.com';
|
||||||
},
|
},
|
||||||
resolveVerificationStep: async (_step, state, _mail, options) => {
|
resolveVerificationStep: async (_step, state, _mail, options) => {
|
||||||
@@ -336,6 +337,7 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
|||||||
|
|
||||||
assert.equal(calls.contentMessages.length, 1);
|
assert.equal(calls.contentMessages.length, 1);
|
||||||
assert.equal(calls.resolvedStates.length, 1);
|
assert.equal(calls.resolvedStates.length, 1);
|
||||||
|
assert.equal(calls.resolveOptions.preserveAccountIdentity, true);
|
||||||
assert.equal(calls.mailStates[0].email, 'new.user@example.com');
|
assert.equal(calls.mailStates[0].email, 'new.user@example.com');
|
||||||
assert.equal(calls.resolvedVerification.state.email, 'new.user@example.com');
|
assert.equal(calls.resolvedVerification.state.email, 'new.user@example.com');
|
||||||
assert.equal(calls.resolvedVerification.options.targetEmail, 'new.user@example.com');
|
assert.equal(calls.resolvedVerification.options.targetEmail, 'new.user@example.com');
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ test('sidepanel html contains account records overlay and manager script', () =>
|
|||||||
assert.match(html, /id="btn-toggle-account-records-selection"/);
|
assert.match(html, /id="btn-toggle-account-records-selection"/);
|
||||||
assert.match(html, /id="btn-delete-selected-account-records"/);
|
assert.match(html, /id="btn-delete-selected-account-records"/);
|
||||||
assert.match(html, /id="input-sub2api-default-proxy"/);
|
assert.match(html, /id="input-sub2api-default-proxy"/);
|
||||||
|
assert.match(html, /id="sub2api-group-picker"/);
|
||||||
|
assert.match(html, /id="input-sub2api-group" value="codex"/);
|
||||||
|
assert.match(html, /id="btn-add-sub2api-group"/);
|
||||||
assert.notEqual(managerIndex, -1);
|
assert.notEqual(managerIndex, -1);
|
||||||
assert.notEqual(sidepanelIndex, -1);
|
assert.notEqual(sidepanelIndex, -1);
|
||||||
assert.ok(managerIndex < sidepanelIndex);
|
assert.ok(managerIndex < sidepanelIndex);
|
||||||
|
|||||||
@@ -512,6 +512,7 @@ function updateAccountRunHistorySettingsUI() {}
|
|||||||
function updatePhoneVerificationSettingsUI() {}
|
function updatePhoneVerificationSettingsUI() {}
|
||||||
function updatePanelModeUI() {}
|
function updatePanelModeUI() {}
|
||||||
function updateMailProviderUI() { calls.push({ target: selectIcloudTargetMailboxType.value, provider: selectIcloudForwardMailProvider.value }); }
|
function updateMailProviderUI() { calls.push({ target: selectIcloudTargetMailboxType.value, provider: selectIcloudForwardMailProvider.value }); }
|
||||||
|
function renderSub2ApiGroupOptions() {}
|
||||||
function isLuckmailProvider() { return false; }
|
function isLuckmailProvider() { return false; }
|
||||||
function updateButtonStates() {}
|
function updateButtonStates() {}
|
||||||
${bundle}
|
${bundle}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
|||||||
|
|
||||||
test('sidepanel source wires runtime signup phone field to background sync messages', () => {
|
test('sidepanel source wires runtime signup phone field to background sync messages', () => {
|
||||||
assert.match(sidepanelSource, /function getRuntimeSignupPhoneValue\(state = latestState\)/);
|
assert.match(sidepanelSource, /function getRuntimeSignupPhoneValue\(state = latestState\)/);
|
||||||
|
assert.match(sidepanelSource, /function shouldExecuteStep3WithSignupPhoneIdentity\(state = latestState\)/);
|
||||||
assert.match(sidepanelSource, /function shouldPreserveSignupPhoneInputValue\(stateSignupPhone = ''\)/);
|
assert.match(sidepanelSource, /function shouldPreserveSignupPhoneInputValue\(stateSignupPhone = ''\)/);
|
||||||
assert.match(sidepanelSource, /function syncSignupPhoneInputFromState\(state = latestState\)/);
|
assert.match(sidepanelSource, /function syncSignupPhoneInputFromState\(state = latestState\)/);
|
||||||
assert.match(sidepanelSource, /async function persistSignupPhoneInputForAction\(\)/);
|
assert.match(sidepanelSource, /async function persistSignupPhoneInputForAction\(\)/);
|
||||||
@@ -143,10 +144,40 @@ test('sidepanel source wires runtime signup phone field to background sync messa
|
|||||||
assert.match(sidepanelSource, /final \? 'SAVE_SIGNUP_PHONE' : 'SET_SIGNUP_PHONE_STATE'/);
|
assert.match(sidepanelSource, /final \? 'SAVE_SIGNUP_PHONE' : 'SET_SIGNUP_PHONE_STATE'/);
|
||||||
assert.match(sidepanelSource, /message\.payload\.signupPhoneNumber !== undefined/);
|
assert.match(sidepanelSource, /message\.payload\.signupPhoneNumber !== undefined/);
|
||||||
assert.match(sidepanelSource, /await persistSignupPhoneInputForAction\(\);\s*await saveSettings/);
|
assert.match(sidepanelSource, /await persistSignupPhoneInputForAction\(\);\s*await saveSettings/);
|
||||||
|
assert.match(sidepanelSource, /if \(shouldExecuteStep3WithSignupPhoneIdentity\(latestState\)\)[\s\S]*payload: \{ step \}/);
|
||||||
assert.match(sidepanelSource, /async function handleSkipStep\(step\)[\s\S]*await persistCurrentSettingsForAction\(\);/);
|
assert.match(sidepanelSource, /async function handleSkipStep\(step\)[\s\S]*await persistCurrentSettingsForAction\(\);/);
|
||||||
assert.match(sidepanelSource, /inputSignupPhone\.addEventListener\('input'[\s\S]*signupPhoneInputDirty = true/);
|
assert.match(sidepanelSource, /inputSignupPhone\.addEventListener\('input'[\s\S]*signupPhoneInputDirty = true/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('manual step 3 uses phone identity without requiring registration email', () => {
|
||||||
|
const api = new Function(`
|
||||||
|
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111', accountIdentifierType: 'phone', accountIdentifier: '+441111111111' };
|
||||||
|
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||||
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
|
function getSelectedSignupMethod() { return 'phone'; }
|
||||||
|
${extractFunction('normalizeSignupMethod')}
|
||||||
|
${extractFunction('getRuntimeSignupPhoneValue')}
|
||||||
|
${extractFunction('shouldExecuteStep3WithSignupPhoneIdentity')}
|
||||||
|
return { shouldExecuteStep3WithSignupPhoneIdentity };
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(api.shouldExecuteStep3WithSignupPhoneIdentity({
|
||||||
|
signupMethod: 'phone',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
accountIdentifierType: 'phone',
|
||||||
|
accountIdentifier: '+441111111111',
|
||||||
|
signupPhoneNumber: '+441111111111',
|
||||||
|
email: '',
|
||||||
|
}), true);
|
||||||
|
assert.equal(api.shouldExecuteStep3WithSignupPhoneIdentity({
|
||||||
|
signupMethod: 'email',
|
||||||
|
accountIdentifierType: 'email',
|
||||||
|
accountIdentifier: 'user@example.com',
|
||||||
|
signupPhoneNumber: '',
|
||||||
|
email: 'user@example.com',
|
||||||
|
}), false);
|
||||||
|
});
|
||||||
|
|
||||||
test('runtime signup phone sync preserves active manual input until it is saved', () => {
|
test('runtime signup phone sync preserves active manual input until it is saved', () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111' };
|
let latestState = { signupMethod: 'phone', phoneVerificationEnabled: true, signupPhoneNumber: '+441111111111' };
|
||||||
|
|||||||
+4
-2
@@ -355,6 +355,8 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功
|
|||||||
补充:
|
补充:
|
||||||
|
|
||||||
- 手机号注册如果在 Step 2 后直接落到短信验证码页、资料页或已登录页,Step 3 会按页面状态自动跳过,不再因为缺少邮箱而失败。
|
- 手机号注册如果在 Step 2 后直接落到短信验证码页、资料页或已登录页,Step 3 会按页面状态自动跳过,不再因为缺少邮箱而失败。
|
||||||
|
- 手动点击 Step 3 时,侧栏会先判断当前统一账号身份:如果本轮是手机号注册,或运行态已经写入 `accountIdentifierType = phone / signupPhoneNumber`,则直接执行 Step 3,不再尝试生成或校验注册邮箱,避免把手机号注册链路误切回邮箱注册链路。
|
||||||
|
- 后台 Step 3 会再次按 `resolveSignupMethod` 与统一账号身份兜底校验;手机号注册意图下如果缺少手机号,会明确提示先完成 Step 2 或填写注册手机号,不会回退使用旧邮箱。
|
||||||
|
|
||||||
### Step 4 / Step 8
|
### Step 4 / Step 8
|
||||||
|
|
||||||
@@ -459,7 +461,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功
|
|||||||
|
|
||||||
1. 先确认当前认证页真实状态,而不是只看 `accountIdentifierType`。
|
1. 先确认当前认证页真实状态,而不是只看 `accountIdentifierType`。
|
||||||
2. 当前是 `add-email`:
|
2. 当前是 `add-email`:
|
||||||
- 调用 `resolveSignupEmailForFlow` 获取或生成本轮绑定邮箱
|
- 调用 `resolveSignupEmailForFlow` 按需获取或生成本轮绑定邮箱;手机号注册不会在 Step 2 提前生成邮箱,只有真实进入 `add-email` 时才占用邮箱资源
|
||||||
- 内容脚本发送 `SUBMIT_ADD_EMAIL` 填写邮箱并提交
|
- 内容脚本发送 `SUBMIT_ADD_EMAIL` 填写邮箱并提交
|
||||||
- 页面进入邮箱验证码页后,用绑定邮箱作为 `step8VerificationTargetEmail`
|
- 页面进入邮箱验证码页后,用绑定邮箱作为 `step8VerificationTargetEmail`
|
||||||
3. 当前是邮箱验证码页:
|
3. 当前是邮箱验证码页:
|
||||||
@@ -479,7 +481,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功
|
|||||||
- 对 `2925 receive` 而言,Step 8 会把当前目标注册邮箱一并传给 2925 内容脚本;只有当邮件里显式写出了其他邮箱时才会跳过,从而在不破坏历史兼容性的前提下,尽量降低误收验证码的概率。
|
- 对 `2925 receive` 而言,Step 8 会把当前目标注册邮箱一并传给 2925 内容脚本;只有当邮件里显式写出了其他邮箱时才会跳过,从而在不破坏历史兼容性的前提下,尽量降低误收验证码的概率。
|
||||||
- 对 `custom` provider 而言,Step 8 仍使用手动验证码确认弹窗;弹窗当前额外提供“出现手机号验证”按钮,点击后会直接抛出与真实 `add-phone` 页面一致的 fatal 错误,供 Auto 按既有 add-phone 分支继续下一邮箱。
|
- 对 `custom` provider 而言,Step 8 仍使用手动验证码确认弹窗;弹窗当前额外提供“出现手机号验证”按钮,点击后会直接抛出与真实 `add-phone` 页面一致的 fatal 错误,供 Auto 按既有 add-phone 分支继续下一邮箱。
|
||||||
- 当登录验证码提交后进入 `phone-verification` 页时,内容脚本会显式把该页面识别为“手机验证码页”,避免与邮箱验证码页混淆。
|
- 当登录验证码提交后进入 `phone-verification` 页时,内容脚本会显式把该页面识别为“手机验证码页”,避免与邮箱验证码页混淆。
|
||||||
- 手机号注册身份本身不会让 Step 8 自动走短信;只有真实页面状态是 `phone_verification_page` 时才调用短信 helper。手机号注册登录后出现 `add-email` 时,Step 8 走“绑定邮箱 -> 邮箱验证码”链路。
|
- 手机号注册身份本身不会让 Step 8 自动走短信;只有真实页面状态是 `phone_verification_page` 时才调用短信 helper。手机号注册登录后出现 `add-email` 时,Step 8 走“按需生成邮箱 -> 绑定邮箱 -> 邮箱验证码”链路。
|
||||||
- `email_in_use` 会在 Step 8 当前步骤内清理当前邮箱并重新打开 `add-email` 获取新邮箱;`max_check_attempts` 不继续点击认证页 `重试`,只恢复当前 Step 8。
|
- `email_in_use` 会在 Step 8 当前步骤内清理当前邮箱并重新打开 `add-email` 获取新邮箱;`max_check_attempts` 不继续点击认证页 `重试`,只恢复当前 Step 8。
|
||||||
|
|
||||||
### Step 9
|
### Step 9
|
||||||
|
|||||||
Reference in New Issue
Block a user