diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 2760ce9..2bbb36d 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -2167,22 +2167,55 @@ function getGpcHelperAutoModeEnabled(state = latestState) { return Boolean(state?.gopayHelperAutoModeEnabled); } -function hasGpcAutoModePermissionField(payload = {}) { - if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { +function normalizeGpcAutoModePermissionValue(value) { + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'number') { + if (value === 1) return true; + if (value === 0) return false; + } + const normalized = String(value ?? '').trim().toLowerCase(); + if (!normalized) { + return null; + } + if (['true', '1', 'yes', 'y', 'on', 'enabled', 'enable'].includes(normalized)) { + return true; + } + if (['false', '0', 'no', 'n', 'off', 'disabled', 'disable'].includes(normalized)) { return false; } - return payload.auto_mode_enabled !== undefined - || payload.autoModeEnabled !== undefined - || payload.auto_enabled !== undefined - || payload.autoEnabled !== undefined - || (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) && hasGpcAutoModePermissionField(payload.data)); + return null; +} + +function getGpcAutoModePermissionFromPayload(payload = {}) { + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + return null; + } + for (const key of ['auto_mode_enabled', 'autoModeEnabled', 'auto_enabled', 'autoEnabled']) { + if (payload[key] !== undefined) { + return normalizeGpcAutoModePermissionValue(payload[key]); + } + } + if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) { + return getGpcAutoModePermissionFromPayload(payload.data); + } + return null; +} + +function shouldPreserveSelectedGpcAutoMode(state = latestState) { + const payloadPermission = getGpcAutoModePermissionFromPayload(state?.gopayHelperBalancePayload); + return normalizeGpcHelperPhoneModeValue(state?.gopayHelperPhoneMode) === GPC_HELPER_PHONE_MODE_AUTO + && (Boolean(state?.gopayHelperAutoModeEnabled) || payloadPermission === true); +} + +function hasGpcAutoModePermissionField(payload = {}) { + return getGpcAutoModePermissionFromPayload(payload) !== null; } function isGpcAutoModePermissionDenied(state = latestState) { - if (getGpcHelperAutoModeEnabled(state)) { - return false; - } - return hasGpcAutoModePermissionField(state?.gopayHelperBalancePayload); + const payloadPermission = getGpcAutoModePermissionFromPayload(state?.gopayHelperBalancePayload); + return payloadPermission === false; } function normalizeGpcRemainingUsesValue(value) { @@ -3389,7 +3422,10 @@ function collectSettingsPayload() { ? selectGpcHelperPhoneMode.value : (latestState?.gopayHelperPhoneMode || 'manual') ); - const effectiveGpcPhoneMode = (typeof isGpcAutoModePermissionDenied === 'function' && isGpcAutoModePermissionDenied(latestState)) + const preserveSelectedGpcAutoMode = typeof shouldPreserveSelectedGpcAutoMode === 'function' + ? shouldPreserveSelectedGpcAutoMode(latestState) + : false; + const effectiveGpcPhoneMode = (!preserveSelectedGpcAutoMode && typeof isGpcAutoModePermissionDenied === 'function' && isGpcAutoModePermissionDenied(latestState)) ? 'manual' : selectedGpcPhoneMode; const selectedGpcOtpChannel = normalizeGpcOtpChannelSafe( @@ -7370,7 +7406,11 @@ function updatePlusModeUI() { ); const gpcAutoModeDenied = isGpcAutoModePermissionDenied(latestState); const gpcAutoModeEnabled = getGpcHelperAutoModeEnabled(latestState); - const isGpcAutoMode = !gpcAutoModeDenied && gpcPhoneMode === GPC_HELPER_PHONE_MODE_AUTO; + const preserveSelectedGpcAutoMode = typeof shouldPreserveSelectedGpcAutoMode === 'function' + ? shouldPreserveSelectedGpcAutoMode(latestState) + : false; + const effectiveGpcAutoModeDenied = gpcAutoModeDenied && !preserveSelectedGpcAutoMode; + const isGpcAutoMode = !effectiveGpcAutoModeDenied && gpcPhoneMode === GPC_HELPER_PHONE_MODE_AUTO; const gpcOtpChannel = normalizeGpcOtpChannelValue( typeof selectGpcHelperOtpChannel !== 'undefined' && selectGpcHelperOtpChannel ? selectGpcHelperOtpChannel.value @@ -7385,7 +7425,7 @@ function updatePlusModeUI() { ? normalizePlusPaymentMethod(selectPlusPaymentMethod.value) : method; const gpcRowsVisible = enabled && selectedMethod === gpcValue; - const canShowGpcModeSelector = gpcRowsVisible && (gpcAutoModeEnabled || !gpcAutoModeDenied); + const canShowGpcModeSelector = gpcRowsVisible && (gpcAutoModeEnabled || !effectiveGpcAutoModeDenied); const localSmsControlsVisible = gpcRowsVisible && !isGpcAutoMode; const effectiveLocalSmsEnabled = !isGpcAutoMode && localSmsEnabled; if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod) { @@ -7430,7 +7470,7 @@ function updatePlusModeUI() { rowGpcHelperPhoneMode.style.display = canShowGpcModeSelector ? '' : 'none'; } if (typeof selectGpcHelperPhoneMode !== 'undefined' && selectGpcHelperPhoneMode) { - selectGpcHelperPhoneMode.value = gpcAutoModeDenied ? GPC_HELPER_PHONE_MODE_MANUAL : gpcPhoneMode; + selectGpcHelperPhoneMode.value = effectiveGpcAutoModeDenied ? GPC_HELPER_PHONE_MODE_MANUAL : gpcPhoneMode; } [ typeof rowGpcHelperCountryCode !== 'undefined' ? rowGpcHelperCountryCode : null, @@ -11765,8 +11805,9 @@ btnGpcHelperBalance?.addEventListener('click', async () => { gopayHelperAutoModeEnabled: getGpcAutoModeEnabledFromResponse(response), gopayHelperApiKeyStatus: response?.apiKeyStatus || response?.data?.status || response?.payload?.data?.status || response?.payload?.status || '', }; + const selectedModeBeforeBalanceState = getSelectedGpcHelperPhoneMode(); syncLatestState(nextState); - if (!nextState.gopayHelperAutoModeEnabled && getSelectedGpcHelperPhoneMode() === GPC_HELPER_PHONE_MODE_AUTO) { + if (!nextState.gopayHelperAutoModeEnabled && selectedModeBeforeBalanceState === GPC_HELPER_PHONE_MODE_AUTO) { selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL; syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL }); await saveSettings({ silent: true, force: true }).catch(() => {}); @@ -13728,7 +13769,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message.payload.gopayHelperPhoneMode !== undefined && selectGpcHelperPhoneMode) { selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(message.payload.gopayHelperPhoneMode); } - if (message.payload.gopayHelperAutoModeEnabled === false && selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO) { + if (message.payload.gopayHelperAutoModeEnabled === false + && selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO + && isGpcAutoModePermissionDenied(latestState)) { selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL; syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL }); showToast('当前 API Key 未开通自动模式,已切回手动模式。', 'warn', 2200); diff --git a/tests/sidepanel-plus-payment-method.test.js b/tests/sidepanel-plus-payment-method.test.js index ecf00ef..92c0e6b 100644 --- a/tests/sidepanel-plus-payment-method.test.js +++ b/tests/sidepanel-plus-payment-method.test.js @@ -134,6 +134,9 @@ test('sidepanel Plus UI hides PayPal account selector while GoPay is selected', extractFunction('getSelectedPlusPaymentMethod'), extractFunction('normalizeGpcHelperPhoneModeValue'), extractFunction('getGpcHelperAutoModeEnabled'), + extractFunction('normalizeGpcAutoModePermissionValue'), + extractFunction('getGpcAutoModePermissionFromPayload'), + extractFunction('shouldPreserveSelectedGpcAutoMode'), extractFunction('hasGpcAutoModePermissionField'), extractFunction('isGpcAutoModePermissionDenied'), extractFunction('normalizeGpcOtpChannelValue'), @@ -217,6 +220,9 @@ test('sidepanel Plus UI shows GPC fields and purchase button only for GPC', () = extractFunction('getSelectedPlusPaymentMethod'), extractFunction('normalizeGpcHelperPhoneModeValue'), extractFunction('getGpcHelperAutoModeEnabled'), + extractFunction('normalizeGpcAutoModePermissionValue'), + extractFunction('getGpcAutoModePermissionFromPayload'), + extractFunction('shouldPreserveSelectedGpcAutoMode'), extractFunction('hasGpcAutoModePermissionField'), extractFunction('isGpcAutoModePermissionDenied'), extractFunction('normalizeGpcOtpChannelValue'), @@ -310,6 +316,9 @@ test('sidepanel hides GPC auto mode selector when API Key has no auto permission extractFunction('getSelectedPlusPaymentMethod'), extractFunction('normalizeGpcHelperPhoneModeValue'), extractFunction('getGpcHelperAutoModeEnabled'), + extractFunction('normalizeGpcAutoModePermissionValue'), + extractFunction('getGpcAutoModePermissionFromPayload'), + extractFunction('shouldPreserveSelectedGpcAutoMode'), extractFunction('hasGpcAutoModePermissionField'), extractFunction('isGpcAutoModePermissionDenied'), extractFunction('normalizeGpcOtpChannelValue'), @@ -353,12 +362,104 @@ return { updatePlusModeUI, selectGpcHelperPhoneMode, plusPaymentMethodCaption, r assert.match(api.plusPaymentMethodCaption.textContent, /手动/); }); +test('sidepanel keeps selected GPC auto mode when persisted permission survives stop refresh', () => { + 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(` +let latestState = { + plusPaymentMethod: 'gpc-helper', + gopayHelperPhoneMode: 'auto', + gopayHelperAutoModeEnabled: true, + gopayHelperBalancePayload: { auto_mode_enabled: true }, +}; +let currentPlusPaymentMethod = 'gpc-helper'; +const inputPlusModeEnabled = { checked: true }; +const selectPlusPaymentMethod = { value: 'gpc-helper', style: { display: 'none' } }; +const GPC_HELPER_PHONE_MODE_AUTO = 'auto'; +const GPC_HELPER_PHONE_MODE_MANUAL = 'manual'; +const plusPaymentMethodCaption = { textContent: '' }; +const rowPayPalAccount = { style: { display: '' } }; +const rowPlusPaymentMethod = { style: { display: 'none' } }; +const rowGpcHelperApi = { style: { display: 'none' } }; +const rowGpcHelperCardKey = { style: { display: 'none' } }; +const rowGpcHelperPhoneMode = { style: { display: 'none' } }; +const selectGpcHelperPhoneMode = { value: 'auto' }; +const rowGpcHelperCountryCode = { style: { display: 'none' } }; +const rowGpcHelperPhone = { style: { display: 'none' } }; +const rowGpcHelperOtpChannel = { style: { display: 'none' } }; +const selectGpcHelperOtpChannel = { value: 'whatsapp' }; +const rowGpcHelperLocalSmsEnabled = { style: { display: 'none' } }; +const inputGpcHelperLocalSmsEnabled = { checked: false }; +const rowGpcHelperLocalSmsUrl = { style: { display: 'none' } }; +const rowGpcHelperPin = { style: { display: 'none' } }; +${bundle} +function syncLatestState(nextState) { latestState = { ...latestState, ...nextState }; } +return { + updatePlusModeUI, + selectGpcHelperPhoneMode, + getSelectedPhoneMode() { return selectGpcHelperPhoneMode.value; }, + getPayloadPhoneMode() { + return (() => { + const selectedGpcPhoneMode = normalizeGpcHelperPhoneModeValue(selectGpcHelperPhoneMode.value); + const preserveSelectedGpcAutoMode = shouldPreserveSelectedGpcAutoMode(latestState); + return (!preserveSelectedGpcAutoMode && isGpcAutoModePermissionDenied(latestState)) ? 'manual' : selectedGpcPhoneMode; + })(); + }, + applyDataUpdated(payload) { + syncLatestState(payload); + if (payload.gopayHelperPhoneMode !== undefined) { + selectGpcHelperPhoneMode.value = normalizeGpcHelperPhoneModeValue(payload.gopayHelperPhoneMode); + } + if (payload.gopayHelperAutoModeEnabled === false + && selectGpcHelperPhoneMode?.value === GPC_HELPER_PHONE_MODE_AUTO + && isGpcAutoModePermissionDenied(latestState)) { + selectGpcHelperPhoneMode.value = GPC_HELPER_PHONE_MODE_MANUAL; + syncLatestState({ gopayHelperPhoneMode: GPC_HELPER_PHONE_MODE_MANUAL }); + } + updatePlusModeUI(); + }, + rows: { rowGpcHelperPhoneMode, rowGpcHelperPhone, rowGpcHelperOtpChannel, rowGpcHelperPin }, +}; +`)(); + + api.updatePlusModeUI(); + assert.equal(api.getSelectedPhoneMode(), 'auto'); + assert.equal(api.getPayloadPhoneMode(), 'auto'); + assert.equal(api.rows.rowGpcHelperPhoneMode.style.display, ''); + assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none'); + + api.applyDataUpdated({ + autoRunning: false, + autoRunPhase: 'stopped', + gopayHelperAutoModeEnabled: false, + }); + + assert.equal(api.getSelectedPhoneMode(), 'auto'); + assert.equal(api.getPayloadPhoneMode(), 'auto'); + assert.equal(api.rows.rowGpcHelperPhone.style.display, 'none'); +}); + test('sidepanel keeps selected GPC auto mode before permission has been queried', () => { const bundle = [ extractFunction('normalizePlusPaymentMethod'), extractFunction('getSelectedPlusPaymentMethod'), extractFunction('normalizeGpcHelperPhoneModeValue'), extractFunction('getGpcHelperAutoModeEnabled'), + extractFunction('normalizeGpcAutoModePermissionValue'), + extractFunction('getGpcAutoModePermissionFromPayload'), + extractFunction('shouldPreserveSelectedGpcAutoMode'), extractFunction('hasGpcAutoModePermissionField'), extractFunction('isGpcAutoModePermissionDenied'), extractFunction('normalizeGpcOtpChannelValue'),