From da6203b8ded00d3d7ecd16dc6102c89c8887dbd3 Mon Sep 17 00:00:00 2001 From: TQCasey <494294315@qq.com> Date: Sat, 30 May 2026 18:28:14 +0800 Subject: [PATCH] fix bugs --- components/ServerSettingsModal.tsx | 157 ++++++++++++++++++++++ screens/HomeScreen.tsx | 200 ++++++++++++----------------- services/api.ts | 82 ++++++++++++ 3 files changed, 324 insertions(+), 115 deletions(-) create mode 100644 components/ServerSettingsModal.tsx diff --git a/components/ServerSettingsModal.tsx b/components/ServerSettingsModal.tsx new file mode 100644 index 0000000..9f66b6b --- /dev/null +++ b/components/ServerSettingsModal.tsx @@ -0,0 +1,157 @@ +import React from 'react'; +import { + Alert, + Modal, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from 'react-native'; +import { saveServerDomain } from '../services/api'; + +const PRESETS = [ + { label: 'aa.pfgame.org', host: 'aa.pfgame.org', port: '443' }, + { label: '192.168.1.117:16000', host: '192.168.1.117', port: '16000' }, +]; + +type Props = { + visible: boolean; + host: string; + port: string; + onHostChange: (host: string) => void; + onPortChange: (port: string) => void; + onClose: () => void; +}; + +export const ServerSettingsModal: React.FC = ({ + visible, + host, + port, + onHostChange, + onPortChange, + onClose, +}) => ( + + + + Server Settings + + {PRESETS.map((p) => ( + { + onHostChange(p.host); + onPortChange(p.port); + }} + style={[styles.presetBtn, host === p.host && port === p.port && styles.presetBtnActive]} + > + + {p.label} + + + ))} + + Host + + Port + + + + Cancel + + { + const domain = port ? `${host}:${port}` : host; + await saveServerDomain(domain, port === '443'); + onClose(); + Alert.alert('Saved', 'Restart app to take effect'); + }} + style={styles.saveBtn} + > + Save + + + + + +); + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'center', + alignItems: 'center', + }, + box: { + backgroundColor: '#fff', + borderRadius: 10, + padding: 20, + width: '85%', + }, + title: { + fontSize: 16, + fontWeight: 'bold', + marginBottom: 12, + }, + presets: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 6, + marginBottom: 14, + }, + presetBtn: { + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 6, + backgroundColor: '#f0f0f0', + }, + presetBtnActive: { backgroundColor: '#3498db' }, + inputLabel: { + fontSize: 13, + color: '#666', + marginBottom: 4, + }, + textInput: { + borderWidth: 1, + borderColor: '#ddd', + borderRadius: 6, + paddingHorizontal: 10, + paddingVertical: 8, + marginBottom: 12, + fontSize: 14, + }, + actions: { + flexDirection: 'row', + justifyContent: 'flex-end', + }, + cancelBtn: { + paddingHorizontal: 16, + paddingVertical: 8, + marginRight: 10, + }, + saveBtn: { + backgroundColor: '#3498db', + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 6, + }, +}); diff --git a/screens/HomeScreen.tsx b/screens/HomeScreen.tsx index 5e9b8be..2ad8ab9 100644 --- a/screens/HomeScreen.tsx +++ b/screens/HomeScreen.tsx @@ -51,12 +51,17 @@ import { AmazonPayOTPBind, } from '../components/WalletBindComponents'; import { WalletSelectModal, WALLET_ICONS, WALLET_TYPE_COLORS } from '../components/WalletSelectModal'; +import { ServerSettingsModal } from '../components/ServerSettingsModal'; import Api, { WalletItem, loadServerDomain, - saveServerDomain, getServerDomain, + getTokenAutoRebindEnabled, + getTokenAutoRebindDebug, + getTokenAutoRebindOptions, + saveTokenAutoRebindEnabled, + saveTokenAutoRebindDebug, } from '../services/api'; function formatWalletTypeLabel(walletType: string) { @@ -124,6 +129,8 @@ interface HomeScreenState { // proxy proxyStatus: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'; proxyError?: string; + tokenAutoRebind: boolean; + tokenAutoRebindDebug: boolean; // server settings showServerSettings: boolean; settingsHost: string; @@ -170,6 +177,8 @@ export default class HomeScreen extends Component { freechargePersonalBindType: 'otpMode', showAmazonPayPersonalBind: false, proxyStatus: 'idle', + tokenAutoRebind: false, + tokenAutoRebindDebug: false, showServerSettings: false, settingsHost: '', settingsPort: '', @@ -190,6 +199,11 @@ export default class HomeScreen extends Component { async componentDidMount() { await loadServerDomain(); + const tokenAutoRebind = getTokenAutoRebindEnabled(); + const tokenAutoRebindDebug = getTokenAutoRebindDebug(); + this.setState({ tokenAutoRebind, tokenAutoRebindDebug }); + proxyBackgroundService.setTokenAutoRebindEnabled(tokenAutoRebind); + proxyBackgroundService.setTokenAutoRebindDebugLog(tokenAutoRebindDebug); await this.setupPermissions(); const doLogin = () => { @@ -229,6 +243,19 @@ export default class HomeScreen extends Component { this.clientId = DeviceInfo.getUniqueIdSync(); const userId = Api.instance.getUserId(); this.setState({ proxyStatus: 'connecting' }); + proxyBackgroundService.configureTokenAutoRebind( + { + listWallets: () => Api.instance.listWallets(), + register: (walletType: WalletType, params: Record) => + Api.instance.register(walletType, params), + getUserToken: () => Api.instance.getUserToken(), + onRebound: () => this.fetchWallets(), + isTokenMode: async (w) => w.otpMode !== true, + }, + getTokenAutoRebindOptions(), + ); + proxyBackgroundService.setTokenAutoRebindEnabled(this.state.tokenAutoRebind); + proxyBackgroundService.setTokenAutoRebindDebugLog(this.state.tokenAutoRebindDebug); await proxyBackgroundService.start({ wsUrl: Api.WS_URL, clientId: this.clientId || '', userId, debug: true, heartbeatInterval: 10000, reconnectInterval: 5000, reconnectMaxAttempts: Infinity, @@ -243,12 +270,25 @@ export default class HomeScreen extends Component { stopProxyClient() { try { + proxyBackgroundService.configureTokenAutoRebind(null); proxyBackgroundService.stop(); } catch { /* ignore */ } } + toggleTokenAutoRebind = async (enabled: boolean) => { + this.setState({ tokenAutoRebind: enabled }); + await saveTokenAutoRebindEnabled(enabled); + proxyBackgroundService.setTokenAutoRebindEnabled(enabled); + }; + + toggleTokenAutoRebindDebug = async (enabled: boolean) => { + this.setState({ tokenAutoRebindDebug: enabled }); + await saveTokenAutoRebindDebug(enabled); + proxyBackgroundService.setTokenAutoRebindDebugLog(enabled); + }; + /** OTP / bind:API catch → { success:false, message } */ private wrapOtpCall = async (fn: () => Promise): Promise => { try { @@ -783,76 +823,6 @@ export default class HomeScreen extends Component { })); }; - renderServerSettingsModal() { - const { showServerSettings, settingsHost, settingsPort } = this.state; - const presets = [ - { label: 'aa.pfgame.org', host: 'aa.pfgame.org', port: '443' }, - { label: '192.168.1.117:16000', host: '192.168.1.117', port: '16000' }, - ]; - return ( - - - - Server Settings - - {presets.map((p) => ( - this.setState({ settingsHost: p.host, settingsPort: p.port })} - style={[s.presetBtn, settingsHost === p.host && settingsPort === p.port && s.presetBtnActive]} - > - - {p.label} - - - ))} - - Host - this.setState({ settingsHost: t })} - placeholder="192.168.1.198" - autoCapitalize="none" - /> - Port - this.setState({ settingsPort: t })} - placeholder="16000" - keyboardType="number-pad" - /> - - this.setState({ showServerSettings: false })} - style={{ paddingHorizontal: 16, paddingVertical: 8, marginRight: 10 }} - > - Cancel - - { - const { settingsHost, settingsPort } = this.state; - const domain = settingsPort ? `${settingsHost}:${settingsPort}` : settingsHost; - await saveServerDomain(domain, settingsPort === '443'); - this.setState({ showServerSettings: false }); - Alert.alert('Saved', 'Restart app to take effect'); - }} - style={s.saveBtn} - > - Save - - - - - - ); - } - renderWalletItem = (item: WalletItem) => { const color = WALLET_TYPE_COLORS[item.walletType] ?? '#888'; const isActive = item.status === 'ACTIVE'; @@ -1032,16 +1002,37 @@ export default class HomeScreen extends Component { error: { label: 'Error', color: '#e74c3c' }, }; const { label, color } = proxyCfg[proxyStatus]; + const { tokenAutoRebind, tokenAutoRebindDebug } = this.state; return ( {/* top bar */} - - - - Proxy {label}{proxyStatus === 'error' && proxyError ? `: ${proxyError}` : ''} - + + + + + Proxy {label}{proxyStatus === 'error' && proxyError ? `: ${proxyError}` : ''} + + + + Token 失效自动重绑 + + + + 重绑调试日志 + + { {this.renderBoundWalletList()} {this.renderBindModal()} - {this.renderServerSettingsModal()} + this.setState({ settingsHost })} + onPortChange={(settingsPort) => this.setState({ settingsPort })} + onClose={() => this.setState({ showServerSettings: false })} + /> this.setState({ showAddWallet: false })} @@ -1104,6 +1102,16 @@ const s = StyleSheet.create({ borderBottomWidth: 1, borderBottomColor: '#e0e0e0', }, + autoRebindRow: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 6, + gap: 8, + }, + autoRebindLabel: { + fontSize: 12, + color: '#666', + }, addBtn: { backgroundColor: '#3498db', borderRadius: 6, @@ -1321,44 +1329,6 @@ const s = StyleSheet.create({ width: 16, textAlign: 'right', }, - settingsBox: { - backgroundColor: '#fff', - borderRadius: 10, - padding: 20, - width: '85%', - }, - settingsTitle: { - fontSize: 16, - fontWeight: 'bold', - marginBottom: 12, - }, - presetBtn: { - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 6, - backgroundColor: '#f0f0f0', - }, - presetBtnActive: { backgroundColor: '#3498db' }, - inputLabel: { - fontSize: 13, - color: '#666', - marginBottom: 4, - }, - textInput: { - borderWidth: 1, - borderColor: '#ddd', - borderRadius: 6, - paddingHorizontal: 10, - paddingVertical: 8, - marginBottom: 12, - fontSize: 14, - }, - saveBtn: { - backgroundColor: '#3498db', - paddingHorizontal: 16, - paddingVertical: 8, - borderRadius: 6, - }, otpBar: { position: 'absolute', bottom: 0, diff --git a/services/api.ts b/services/api.ts index 4117c74..b5a66ad 100644 --- a/services/api.ts +++ b/services/api.ts @@ -13,6 +13,24 @@ export interface WalletItem { const DEFAULT_DOMAIN = 'aa.pfgame.org'; const STORAGE_KEY = 'server_domain'; const HTTPS_KEY = 'server_https'; +const TOKEN_AUTO_REBIND_KEY = 'token_auto_rebind_enabled'; +const TOKEN_AUTO_REBIND_SCAN_MS_KEY = 'token_auto_rebind_scan_ms'; +const TOKEN_AUTO_REBIND_COOLDOWN_MS_KEY = 'token_auto_rebind_cooldown_ms'; +const TOKEN_AUTO_REBIND_FAIL_COOLDOWN_MS_KEY = 'token_auto_rebind_fail_cooldown_ms'; +const TOKEN_AUTO_REBIND_DEBUG_KEY = 'token_auto_rebind_debug'; + +/** 扫 list 间隔 */ +const DEFAULT_TOKEN_AUTO_REBIND_SCAN_MS = 1 * 60 * 1000; +/** 重绑成功后冷却 */ +const DEFAULT_TOKEN_AUTO_REBIND_COOLDOWN_MS = 1 * 60 * 1000; +/** 重绑失败后冷却 */ +const DEFAULT_TOKEN_AUTO_REBIND_FAIL_COOLDOWN_MS = 1 * 60 * 1000; + +let _tokenAutoRebindEnabled = false; +let _tokenAutoRebindDebug = false; +let _tokenAutoRebindScanMs = DEFAULT_TOKEN_AUTO_REBIND_SCAN_MS; +let _tokenAutoRebindCooldownMs = DEFAULT_TOKEN_AUTO_REBIND_COOLDOWN_MS; +let _tokenAutoRebindFailCooldownMs = DEFAULT_TOKEN_AUTO_REBIND_FAIL_COOLDOWN_MS; let _domain = DEFAULT_DOMAIN; let _useHttps = true; @@ -22,10 +40,74 @@ export async function loadServerDomain(): Promise { if (saved) _domain = saved; const https = await AsyncStorage.getItem(HTTPS_KEY); if (https !== null) _useHttps = https === 'true'; + const autoRebind = await AsyncStorage.getItem(TOKEN_AUTO_REBIND_KEY); + _tokenAutoRebindEnabled = autoRebind === 'true'; + const scanMs = await AsyncStorage.getItem(TOKEN_AUTO_REBIND_SCAN_MS_KEY); + const cooldownMs = await AsyncStorage.getItem(TOKEN_AUTO_REBIND_COOLDOWN_MS_KEY); + if (scanMs) { + const n = parseInt(scanMs, 10); + if (Number.isFinite(n) && n > 0) _tokenAutoRebindScanMs = n; + } + if (cooldownMs) { + const n = parseInt(cooldownMs, 10); + if (Number.isFinite(n) && n > 0) _tokenAutoRebindCooldownMs = n; + } + const failCooldownMs = await AsyncStorage.getItem(TOKEN_AUTO_REBIND_FAIL_COOLDOWN_MS_KEY); + if (failCooldownMs) { + const n = parseInt(failCooldownMs, 10); + if (Number.isFinite(n) && n > 0) _tokenAutoRebindFailCooldownMs = n; + } + const debug = await AsyncStorage.getItem(TOKEN_AUTO_REBIND_DEBUG_KEY); + _tokenAutoRebindDebug = debug === 'true'; console.log('loadServerDomain', _domain, 'https:', _useHttps); return _domain; } +export function getTokenAutoRebindOptions(): { + scanIntervalMs: number; + cooldownMs: number; + failCooldownMs: number; + debugLog: boolean; +} { + return { + scanIntervalMs: _tokenAutoRebindScanMs, + cooldownMs: _tokenAutoRebindCooldownMs, + failCooldownMs: _tokenAutoRebindFailCooldownMs, + debugLog: _tokenAutoRebindDebug, + }; +} + +export function getTokenAutoRebindDebug(): boolean { + return _tokenAutoRebindDebug; +} + +export async function saveTokenAutoRebindDebug(enabled: boolean): Promise { + _tokenAutoRebindDebug = enabled; + await AsyncStorage.setItem(TOKEN_AUTO_REBIND_DEBUG_KEY, String(enabled)); +} + +export async function saveTokenAutoRebindOptions( + scanIntervalMs: number, + cooldownMs: number, + failCooldownMs: number = DEFAULT_TOKEN_AUTO_REBIND_FAIL_COOLDOWN_MS, +): Promise { + _tokenAutoRebindScanMs = scanIntervalMs; + _tokenAutoRebindCooldownMs = cooldownMs; + _tokenAutoRebindFailCooldownMs = failCooldownMs; + await AsyncStorage.setItem(TOKEN_AUTO_REBIND_SCAN_MS_KEY, String(scanIntervalMs)); + await AsyncStorage.setItem(TOKEN_AUTO_REBIND_COOLDOWN_MS_KEY, String(cooldownMs)); + await AsyncStorage.setItem(TOKEN_AUTO_REBIND_FAIL_COOLDOWN_MS_KEY, String(failCooldownMs)); +} + +export function getTokenAutoRebindEnabled(): boolean { + return _tokenAutoRebindEnabled; +} + +export async function saveTokenAutoRebindEnabled(enabled: boolean): Promise { + _tokenAutoRebindEnabled = enabled; + await AsyncStorage.setItem(TOKEN_AUTO_REBIND_KEY, String(enabled)); +} + export async function saveServerDomain(domain: string, useHttps: boolean): Promise { _domain = domain; _useHttps = useHttps;