修复注册流程不写入持久化登录态、注册失败500错误显示原始英文

Register组件缺少Login同款的keep-logged-in挂载副作用,导致注册产生的
新账号在下一次刷新时静默丢失会话(无报错、无跳转登录页)。
另外注册走原生WS {acc}协议而非REST,未接入CTRL_ERROR_TEXT_CN翻译表,
"internal error (500)"等错误会原样展示给用户。

第十轮最小粒度测试 Ch1-2 发现。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-06 00:46:27 +08:00
co-authored by Claude Sonnet 5
parent eb4c1cd8da
commit 35f659249e
2 changed files with 27 additions and 4 deletions
+16 -2
View File
@@ -383,7 +383,7 @@ function VerificationButton({phone, scene = 'register', onChallenge, onError}) {
// no phone-vs-username mode toggle. "Phone takes precedence" (per the spec text) is validation-only: // no phone-vs-username mode toggle. "Phone takes precedence" (per the spec text) is validation-only:
// if a phone is filled it must be verified; the invite code is only required for a username-only // if a phone is filled it must be verified; the invite code is only required for a username-only
// signup (matching the mock's original submit-time validation, now with real backend calls behind it). // signup (matching the mock's original submit-time validation, now with real backend calls behind it).
function Register({errorText, errorLevel, onImRegisterRequest}) { function Register({errorText, errorLevel, persist, onImRegisterRequest, onPersistenceChange}) {
const [form, setForm] = React.useState({username: '', phone: '', code: '', password: '', invite: ''}); const [form, setForm] = React.useState({username: '', phone: '', code: '', password: '', invite: ''});
const [challengeId, setChallengeId] = React.useState(''); const [challengeId, setChallengeId] = React.useState('');
const [agreed, setAgreed] = React.useState(false); const [agreed, setAgreed] = React.useState(false);
@@ -392,6 +392,19 @@ function Register({errorText, errorLevel, onImRegisterRequest}) {
const change = key => e => setForm({...form, [key]: e.target.value}); const change = key => e => setForm({...form, [key]: e.target.value});
const usingPhone = !!form.phone; const usingPhone = !!form.phone;
// Same silent "stay logged in" opt-in Login has (see its own copy of this effect for why there's
// no visible checkbox) -- missing here meant every account created by registering (as opposed to
// logging in) never had keep-logged-in/auth-token set, so handleLoginSuccessful() never persisted
// the session: the very next refresh silently lost myUserId with no redirect to the login page
// and no error, just a permanently empty messages-home shell. Every fresh registration hit this on
// its first reload.
React.useEffect(() => {
if (!persist) {
onPersistenceChange(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// onImRegisterRequest (tinode-web.jsx's handleImRegisterRequest -> a WS connect+createAccountBasic // onImRegisterRequest (tinode-web.jsx's handleImRegisterRequest -> a WS connect+createAccountBasic
// chain) isn't awaited and doesn't return a promise -- on the username/invite-code path (no // chain) isn't awaited and doesn't return a promise -- on the username/invite-code path (no
// verifyRegisterCode await ahead of it) `submit` returns and its `finally` clears `submitting` // verifyRegisterCode await ahead of it) `submit` returns and its `finally` clears `submitting`
@@ -795,7 +808,8 @@ export default function AuthOnboardingView({
errorText={errorText} errorLevel={errorLevel} onLoginRequest={onLoginRequest} onPersistenceChange={onPersistenceChange} onError={onError} />; errorText={errorText} errorLevel={errorLevel} onLoginRequest={onLoginRequest} onPersistenceChange={onPersistenceChange} onError={onError} />;
case 'enterprise-register': return <EnterpriseRegister />; case 'enterprise-register': return <EnterpriseRegister />;
case 'enterprise-feedback': return <EnterpriseFeedback />; case 'enterprise-feedback': return <EnterpriseFeedback />;
case 'register': return <Register errorText={errorText} errorLevel={errorLevel} onImRegisterRequest={onImRegisterRequest} />; case 'register': return <Register errorText={errorText} errorLevel={errorLevel} persist={persist}
onImRegisterRequest={onImRegisterRequest} onPersistenceChange={onPersistenceChange} />;
case 'reset': return <PasswordReset />; case 'reset': return <PasswordReset />;
case 'identity-check': return <IdentityCheck authenticated={authenticated} hashParams={hashParams} />; case 'identity-check': return <IdentityCheck authenticated={authenticated} hashParams={hashParams} />;
case 'realname': return <Realname authenticated={authenticated} />; case 'realname': return <Realname authenticated={authenticated} />;
+11 -2
View File
@@ -177,7 +177,8 @@ const CTRL_ERROR_TEXT_CN = {
'authentication required': '请先登录', 'authentication required': '请先登录',
'unknown authentication scheme': '登录方式不受支持,请刷新页面重试', 'unknown authentication scheme': '登录方式不受支持,请刷新页面重试',
'policy violation': '当前操作不符合安全策略要求', 'policy violation': '当前操作不符合安全策略要求',
'permission denied': '没有权限执行此操作,请重新登录' 'permission denied': '没有权限执行此操作,请重新登录',
'internal error': '服务器繁忙,请稍后重试'
}; };
function translateCtrlError(err) { function translateCtrlError(err) {
const raw = err && err.message; const raw = err && err.message;
@@ -1517,7 +1518,15 @@ class TinodeWeb extends React.Component {
this.handleLoginSuccessful(this); this.handleLoginSuccessful(this);
} }
}).catch(err => { }).catch(err => {
this.handleError(err.message, 'err'); // Registration goes over the native WS {acc} protocol, not REST, so im-api.js's
// REASON_MESSAGES translation table (REST-only) never sees this error -- it was showing
// raw SDK text like "internal error (500)" verbatim (confirmed live: retrying a phone
// number that's already registered). CTRL_ERROR_TEXT_CN already translates this same class
// of {ctrl} error for login; reused here with a registration-appropriate fallback instead
// of loginErrorMessage's own (its 401/403 fallback is "账号或密码错误", which doesn't fit a
// failed registration).
const known = translateCtrlError(err);
this.handleError(known || (err && err.message) || '注册失败,请稍后重试', 'err');
}); });
} }