fix: 代码质量清理 — XWebView Pressable/Clipboard、configCrypto 消除 any、UpdateSDK AsyncStorage 优化

- XWebViewView/XWebViewScreen: TouchableOpacity → Pressable
- XWebViewView/XWebViewScreen: Clipboard 改用 @react-native-clipboard/clipboard
- configCrypto.ts: 6 处 any 替换为 SubtleCryptoLike 接口
- UpdateSDK: CachedRnBundle 移除 source 字段,AsyncStorage 仅存版本元数据
- xwebview 添加 @react-native-clipboard/clipboard peerDependency
这个提交包含在:
XuqmGroup 2026-06-16 14:48:47 +08:00
父节点 611f2ba95d
当前提交 b6581adc51
共有 6 个文件被更改,包括 34 次插入34 次删除

查看文件

@ -28,6 +28,7 @@
"@react-native-async-storage/async-storage": "^2.1.2",
"axios": "^1.7.0",
"react": "^19.0.0",
"react-native": "^0.85.0",
"zod": "^3.23.0"
}
}

查看文件

@ -28,10 +28,15 @@ export interface DecryptedConfig {
expiresAt?: string
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getSubtle(): any {
interface SubtleCryptoLike {
importKey(format: string, keyData: BufferSource, algorithm: string | Record<string, unknown>, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>
deriveKey(algorithm: string | Record<string, unknown>, baseKey: CryptoKey, derivedKeyAlgorithm: string | Record<string, unknown>, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>
decrypt(algorithm: string | Record<string, unknown>, key: CryptoKey, data: BufferSource): Promise<ArrayBuffer>
}
function getSubtle(): SubtleCryptoLike {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const qc = require('react-native-quick-crypto') as any
const qc = require('react-native-quick-crypto') as { subtle?: SubtleCryptoLike; default?: { subtle?: SubtleCryptoLike } }
const subtle = qc.subtle ?? qc.default?.subtle
if (!subtle) throw new Error('[XuqmSDK] react-native-quick-crypto not available')
return subtle
@ -43,8 +48,7 @@ function base64UrlDecode(s: string): Uint8Array {
return Uint8Array.from({ length: binary.length }, (_, i) => binary.charCodeAt(i))
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function deriveKey(salt: Uint8Array, passphrase: string): Promise<any> {
async function deriveKey(salt: Uint8Array, passphrase: string): Promise<CryptoKey> {
const subtle = getSubtle()
const passphraseKey = await subtle.importKey(
'raw',
@ -54,8 +58,7 @@ async function deriveKey(salt: Uint8Array, passphrase: string): Promise<any> {
['deriveKey'],
)
return subtle.deriveKey(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{ name: 'PBKDF2', salt: salt as any, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },
{ name: 'PBKDF2', salt: salt as unknown as BufferSource, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },
passphraseKey,
{ name: 'AES-GCM', length: 256 },
false,
@ -76,8 +79,7 @@ export async function decryptConfigFile(content: string): Promise<DecryptedConfi
const passphrase = getPassphrase(magic)
const key = await deriveKey(salt, passphrase)
const subtle = getSubtle()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const plainBuffer = await subtle.decrypt({ name: 'AES-GCM', iv } as any, key, ciphertext as any)
const plainBuffer = await subtle.decrypt({ name: 'AES-GCM', iv: iv as unknown as BufferSource }, key, ciphertext as unknown as BufferSource)
const json = new TextDecoder().decode(plainBuffer)
return JSON.parse(json) as DecryptedConfig
}

查看文件

@ -55,7 +55,6 @@ interface CachedRnBundle {
version: string
md5: string
downloadedAt: string
source: string
}
// ─── Internal state ────────────────────────────────────────────────────────────
@ -435,12 +434,11 @@ export const UpdateSDK = {
}
}
// 写入 AsyncStorage 缓存(版本记录
// 写入 AsyncStorage 缓存(仅版本元数据,不含 bundle 源码
const cachePayload: CachedRnBundle = {
moduleId,
version: info.latestVersion,
md5: info.md5,
source,
downloadedAt: new Date().toISOString(),
}
await AsyncStorage.setItem(bundleCacheKey(moduleId), JSON.stringify(cachePayload))

查看文件

@ -32,6 +32,8 @@
"@types/react": "^19.0.0",
"@react-navigation/native": "^7.0.0",
"@react-native-async-storage/async-storage": "^1.21.0",
"@react-native-clipboard/clipboard": "^1.16.3",
"react-native-safe-area-context": "^5.4.0",
"typescript": "^5.9.3"
}
}

查看文件

@ -9,16 +9,16 @@ import {
ActionSheetIOS,
Alert,
BackHandler,
Clipboard,
Linking,
Platform,
Pressable,
StatusBar,
StyleSheet,
Text,
ToastAndroid,
TouchableOpacity,
View,
} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import { SafeAreaView } from 'react-native-safe-area-context'
import WebView from 'react-native-webview'
import type {
@ -82,13 +82,13 @@ function HeaderBackClose({
return (
<View style={styles.headerLeft}>
{canGoBack ? (
<TouchableOpacity style={styles.headerBtn} onPress={onBack} hitSlop={HIT_SLOP}>
<Pressable style={styles.headerBtn} onPress={onBack} hitSlop={HIT_SLOP}>
<IconBack size={22} color="#222222" />
</TouchableOpacity>
</Pressable>
) : null}
<TouchableOpacity style={styles.headerBtn} onPress={onClose} hitSlop={HIT_SLOP}>
<Pressable style={styles.headerBtn} onPress={onClose} hitSlop={HIT_SLOP}>
<IconClose size={24} color="#222222" />
</TouchableOpacity>
</Pressable>
</View>
)
}
@ -144,20 +144,20 @@ function MenuButton({
if (config.clickMenu) {
const { clickMenu } = config
return (
<TouchableOpacity
<Pressable
style={styles.headerBtn}
onPress={clickMenu.onClick}
hitSlop={HIT_SLOP}
>
{clickMenu.view ?? <IconMenu size={22} />}
</TouchableOpacity>
</Pressable>
)
}
return (
<TouchableOpacity style={styles.headerBtn} onPress={handleDefaultMenu} hitSlop={HIT_SLOP}>
<Pressable style={styles.headerBtn} onPress={handleDefaultMenu} hitSlop={HIT_SLOP}>
<IconMenu size={22} />
</TouchableOpacity>
</Pressable>
)
}
@ -526,7 +526,7 @@ export function XWebViewScreen() {
<View style={styles.errorContainer}>
<Text style={styles.errorTitle}></Text>
<Text style={styles.errorMessage}>{loadError}</Text>
<TouchableOpacity
<Pressable
style={styles.retryBtn}
onPress={() => {
setLoadError(null)
@ -534,7 +534,7 @@ export function XWebViewScreen() {
}}
>
<Text style={styles.retryText}></Text>
</TouchableOpacity>
</Pressable>
</View>
) : (
<WebViewAny

查看文件

@ -1,14 +1,14 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
import {
Alert,
Clipboard,
Platform,
Pressable,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import { SafeAreaView } from 'react-native-safe-area-context'
import WebView from 'react-native-webview'
import type {
@ -336,15 +336,12 @@ export function XWebViewView() {
<View style={styles.errorContainer}>
<Text style={styles.errorTitle}></Text>
<Text style={styles.errorMessage}>{loadError}</Text>
<TouchableOpacity
style={styles.retryBtn}
onPress={() => {
setLoadError(null)
webViewRef.current?.reload()
}}
<Pressable
onPress={() => webViewRef.current?.reload()}
style={({ pressed }) => [styles.retryBtn, pressed && { opacity: 0.7 }]}
>
<Text style={styles.retryText}></Text>
</TouchableOpacity>
<Text style={styles.retryText}></Text>
</Pressable>
</View>
) : (
<WebViewAny