diff --git a/background.js b/background.js index 02ef4c82..a5c43b52 100644 --- a/background.js +++ b/background.js @@ -10934,6 +10934,72 @@ async function reportCompletedStepSideEffectError(step, error) { return reportCompletedNodeSideEffectError(getNodeIdByStepForState(step, state), error); } +function getSignupPhoneIdentityValue(state = {}) { + const accountIdentifierType = String(state?.accountIdentifierType || '').trim().toLowerCase(); + return String( + state?.signupPhoneNumber + || state?.signupPhoneCompletedActivation?.phoneNumber + || state?.signupPhoneActivation?.phoneNumber + || (accountIdentifierType === 'phone' ? state?.accountIdentifier : '') + || '' + ).trim(); +} + +function signupPhoneIdentityValuesMatch(left = '', right = '') { + const leftRaw = String(left || '').trim(); + const rightRaw = String(right || '').trim(); + if (!leftRaw || !rightRaw) { + return false; + } + if (leftRaw === rightRaw) { + return true; + } + const leftDigits = leftRaw.replace(/\D+/g, ''); + const rightDigits = rightRaw.replace(/\D+/g, ''); + return Boolean(leftDigits && rightDigits && leftDigits === rightDigits); +} + +async function clearSignupPhoneIdentityAfterSuccessfulFlow(completionState = {}, options = {}) { + const completedPhone = getSignupPhoneIdentityValue(completionState); + const latestState = await getState(); + const currentPhone = getSignupPhoneIdentityValue(latestState); + const currentIdentifierType = String(latestState?.accountIdentifierType || '').trim().toLowerCase(); + const hasCurrentPhoneIdentity = Boolean( + currentPhone + || latestState?.signupPhoneActivation + || latestState?.signupPhoneCompletedActivation + || currentIdentifierType === 'phone' + ); + + if (!hasCurrentPhoneIdentity) { + return { cleared: false, reason: 'empty' }; + } + + if (completedPhone && currentPhone && !signupPhoneIdentityValuesMatch(completedPhone, currentPhone)) { + return { cleared: false, reason: 'changed' }; + } + + const updates = { + phoneNumber: '', + signupPhoneNumber: '', + signupPhoneActivation: null, + signupPhoneCompletedActivation: null, + signupPhoneVerificationRequestedAt: null, + signupPhoneVerificationPurpose: '', + }; + if (currentIdentifierType === 'phone') { + updates.accountIdentifierType = null; + updates.accountIdentifier = ''; + } + + await setState(updates); + broadcastDataUpdate(updates); + await addLog('手机号注册:流程成功后已清空本轮注册手机号,避免下一轮复用。', 'ok', { + nodeId: options?.nodeId || 'platform-verify', + }); + return { cleared: true, phoneNumber: currentPhone || completedPhone }; +} + async function runCompletedNodeSideEffects(nodeId, payload, completionState, lastNodeId) { await handleNodeData(nodeId, payload); if (nodeId === lastNodeId) { @@ -10966,6 +11032,11 @@ async function completeNodeFromBackground(nodeId, payload = {}) { await addLog('已完成', 'ok', { nodeId: normalizedNodeId }); if (normalizedNodeId === lastNodeId) { + if (typeof clearSignupPhoneIdentityAfterSuccessfulFlow === 'function') { + await clearSignupPhoneIdentityAfterSuccessfulFlow(latestState, { + nodeId: normalizedNodeId, + }); + } notifyNodeComplete(normalizedNodeId, payload); void runCompletedNodeSideEffects(normalizedNodeId, payload, completionState, lastNodeId) .catch((error) => reportCompletedNodeSideEffectError(normalizedNodeId, error)); diff --git a/tests/background-step-completion.test.js b/tests/background-step-completion.test.js index 7c7d38a6..e4169b14 100644 --- a/tests/background-step-completion.test.js +++ b/tests/background-step-completion.test.js @@ -51,17 +51,18 @@ function extractFunction(name) { return source.slice(start, end); } -function createApi(events, lastNodeId = 'platform-verify') { - return new Function('events', 'lastNodeId', ` +function createApi(events, lastNodeId = 'platform-verify', initialState = {}) { + return new Function('events', 'lastNodeId', 'initialState', ` let stopRequested = false; const LOG_PREFIX = '[test]'; +let currentState = { nodeStatuses: {}, accountContributionEnabled: true, ...initialState }; const STOP_ERROR_MESSAGE = '流程已被用户停止。'; function getErrorMessage(error) { return error?.message || String(error || ''); } async function getState() { events.push({ type: 'getState' }); - return { nodeStatuses: {}, accountContributionEnabled: true }; + return currentState; } function getLastNodeIdForState() { return lastNodeId; @@ -69,6 +70,13 @@ function getLastNodeIdForState() { async function setNodeStatus(nodeId, status) { events.push({ type: 'status', nodeId, status }); } +async function setState(updates) { + currentState = { ...currentState, ...updates }; + events.push({ type: 'setState', updates }); +} +function broadcastDataUpdate(updates) { + events.push({ type: 'broadcast', updates }); +} async function addLog(message, level, options = {}) { events.push({ type: 'log', message, level, options }); } @@ -89,11 +97,14 @@ async function handleNodeData(nodeId, payload) { async function appendAndBroadcastAccountRunRecord(status, state) { events.push({ type: 'record', status, state }); } +${extractFunction('getSignupPhoneIdentityValue')} +${extractFunction('signupPhoneIdentityValuesMatch')} +${extractFunction('clearSignupPhoneIdentityAfterSuccessfulFlow')} ${extractFunction('runCompletedNodeSideEffects')} ${extractFunction('reportCompletedNodeSideEffectError')} ${extractFunction('completeNodeFromBackground')} -return { completeNodeFromBackground }; -`)(events, lastNodeId); +return { completeNodeFromBackground, getCurrentState: () => currentState }; +`)(events, lastNodeId, initialState); } test('completeNodeFromBackground releases final node before slow post-completion side effects', async () => { @@ -124,3 +135,27 @@ test('completeNodeFromBackground keeps non-final node data handling before compl assert.equal(types.indexOf('handle-done') < types.indexOf('notify'), true); assert.equal(types.includes('record'), false); }); + +test('completeNodeFromBackground clears signup phone identity before releasing final node', async () => { + const events = []; + const api = createApi(events, 'platform-verify', { + accountIdentifierType: 'phone', + accountIdentifier: '+56979206303', + signupPhoneNumber: '+56979206303', + signupPhoneActivation: { activationId: 'active', phoneNumber: '+56979206303' }, + signupPhoneCompletedActivation: { activationId: 'done', phoneNumber: '+56979206303' }, + signupPhoneVerificationRequestedAt: 123, + signupPhoneVerificationPurpose: 'login', + }); + + await api.completeNodeFromBackground('platform-verify', { localhostUrl: 'http://localhost:1455/auth/callback?code=ok' }); + + const types = events.map((event) => event.type); + assert.equal(types.indexOf('setState') < types.indexOf('notify'), true); + assert.equal(types.indexOf('broadcast') < types.indexOf('notify'), true); + assert.equal(api.getCurrentState().signupPhoneNumber, ''); + assert.equal(api.getCurrentState().signupPhoneActivation, null); + assert.equal(api.getCurrentState().signupPhoneCompletedActivation, null); + assert.equal(api.getCurrentState().accountIdentifierType, null); + assert.equal(api.getCurrentState().accountIdentifier, ''); +});