From 8dd0edd2cee28cbc371420596cae4a3b553143d5 Mon Sep 17 00:00:00 2001 From: yecong Date: Wed, 3 Jun 2026 14:35:08 +0800 Subject: [PATCH] =?UTF-8?q?20260603-=E2=91=A0=20=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=A1=A8=E9=A2=91=E5=99=A8=EF=BC=8C=E5=81=9A=E4=BA=86=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E5=92=8C=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E5=B7=B2=E7=BB=8F=E5=86=85=E6=B5=8Bok=E3=80=82=20=E2=91=A1=20?= =?UTF-8?q?=E9=80=82=E9=85=8DT5=E9=AA=91=E8=A1=8C=E5=8F=B0=EF=BC=8C?= =?UTF-8?q?=E5=81=9A=E4=BA=86=E7=9B=B8=E5=85=B3=E9=A1=B5=E9=9D=A2=E3=80=82?= =?UTF-8?q?=E5=86=85=E6=B5=8B=E8=BF=98=E4=B8=8D=E5=A4=9F=E5=85=85=E5=88=86?= =?UTF-8?q?=EF=BC=8C=E4=BA=A7=E5=93=81=E4=B9=9F=E4=B8=8D=E7=9D=80=E6=80=A5?= =?UTF-8?q?=E4=B8=8A=E5=B8=82=EF=BC=8C=E6=89=80=E4=BB=A5=E6=AD=A4=E7=89=88?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=AD=EF=BC=8C=E5=BA=94=E8=AF=A5=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=9A=90=E8=97=8F=E6=8E=89=E3=80=82=20=E2=91=A2=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E4=B9=8B=E5=89=8D=20=E7=9B=98?= =?UTF-8?q?=E7=88=AA=E8=AE=BE=E5=A4=87=E4=BF=A1=E6=81=AF=20=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E6=97=B6=EF=BC=8C=E6=9C=89=E6=A6=82=E7=8E=87=E5=87=BA?= =?UTF-8?q?=E7=8E=B0=E4=B9=B1=E7=A0=81=E7=9A=84=E6=83=85=E5=86=B5=E3=80=82?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E6=97=B6=E5=81=9A=E4=BA=86=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=92=8C=E5=BB=B6=E8=BF=9F=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E6=AD=A4=E9=97=AE=E9=A2=98=E3=80=82=20=E2=91=A3=20=E5=A4=9A?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E6=88=91=E4=BB=AC=E8=BF=99=E8=BE=B9=E5=81=9A?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=B6=EF=BC=8C=E6=8B=89=E5=8F=96=E7=9A=84?= =?UTF-8?q?=E6=98=AF=E4=BB=85=E6=9C=89=E4=B8=AD=E8=8B=B1=E6=96=87=E7=9A=84?= =?UTF-8?q?=E7=89=88=E6=9C=AC=EF=BC=8C=E5=BD=93=E6=97=B6=E4=BD=A0=E4=BB=AC?= =?UTF-8?q?=E9=82=A3=E5=A5=BD=E5=83=8F=E6=9C=89=E6=9B=B4=E6=96=B0=E6=9B=B4?= =?UTF-8?q?=E5=A4=9A=E8=AF=AD=E8=A8=80=EF=BC=9F=E6=89=80=E4=BB=A5=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=80=82=E9=85=8D=E4=B8=80=E4=B8=8B=E3=80=82=20?= =?UTF-8?q?=E2=91=A4=20=E8=93=9D=E7=89=99=E5=90=8D=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E9=80=82=E9=85=8D=E6=9C=80=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E5=9B=BA=E4=BB=B6=E5=90=8D=E7=A7=B0=EF=BC=9A=20=E7=9B=98?= =?UTF-8?q?=E7=88=AA=E4=B8=BA=EF=BC=9APF-PM5-=E5=89=8D=E7=BC=80=EF=BC=8C?= =?UTF-8?q?=E6=A1=A8=E9=A2=91=E5=99=A8=E4=B8=BAPF-STK-=E5=89=8D=E7=BC=80?= =?UTF-8?q?=EF=BC=8C=E9=AA=91=E8=A1=8C=E5=8F=B0=E4=B8=BAPF-T5-=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E3=80=82=E5=90=8C=E6=97=B6=E5=85=81=E8=AE=B8=E5=89=8D?= =?UTF-8?q?=E7=BC=80POWERFUN-=E4=B9=9F=E8=83=BD=E6=98=BE=E7=A4=BA=E5=B9=B6?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.tsx | 83 +- ios/Podfile.lock | 4 +- ios/dfuapp.xcodeproj/project.pbxproj | 16 +- ios/dfuapp/Info.plist | 8 +- package-lock.json | 741 +++++----- package.json | 9 +- src/DfuScreen.tsx | 348 ++++- src/HomeScreen.tsx | 14 +- src/InfoScreen.tsx | 462 ++++-- src/InfoScreen2.tsx | 1227 +++++++++++++++ src/InfoScreen3.tsx | 2049 ++++++++++++++++++++++++++ src/ScanScreen.tsx | 6 +- src/ScanScreen2.tsx | 238 +++ src/ScanScreen3.tsx | 231 +++ src/SpindownScreen.tsx | 796 ++++++++++ src/helper/ftmsIndoorBikeDataBus.ts | 315 ++++ src/i18n/index.ts | 4 +- src/i18n/locales/en.json | 142 +- src/i18n/locales/zh.json | 155 +- yarn.lock | 781 +++++----- 20 files changed, 6690 insertions(+), 939 deletions(-) create mode 100644 src/InfoScreen2.tsx create mode 100644 src/InfoScreen3.tsx create mode 100644 src/ScanScreen2.tsx create mode 100644 src/ScanScreen3.tsx create mode 100644 src/SpindownScreen.tsx create mode 100644 src/helper/ftmsIndoorBikeDataBus.ts diff --git a/App.tsx b/App.tsx index b165b62..7caf0d4 100644 --- a/App.tsx +++ b/App.tsx @@ -1,23 +1,55 @@ +console.log("🔥 当前 App.tsx 已加载"); import * as React from "react"; import { NavigationContainer } from "@react-navigation/native"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; import HomeScreen from "./src/HomeScreen"; import ScanScreen from "./src/ScanScreen"; +import ScanScreen2 from "./src/ScanScreen2"; +import ScanScreen3 from "./src/ScanScreen3"; import InfoScreen from "./src/InfoScreen"; import DfuScreen from "./src/DfuScreen"; import PrivacyScreen from "./src/PrivacyScreen"; import SplashScreen from "./src/SplashScreen"; // ✅ 新增启动页 -import pxToDp from "./src/helper/pxToDp"; import SettingScreen from "./src/SettingScreen"; import './src/i18n' +import InfoScreen2 from "./src/InfoScreen2"; +import InfoScreen3 from "./src/InfoScreen3"; +import SpindownScreen from "./src/SpindownScreen"; +import { decode } from "base-64"; + + +// 不要 global.atob +// 不要 atob + +const base64ToBytes = (base64: string): number[] => { + const binary = decode(base64); + const bytes: number[] = []; + + for (let i = 0; i < binary.length; i++) { + bytes.push(binary.charCodeAt(i)); + } + + return bytes; +}; export type RootStackParamList = { Splash: undefined; Home: undefined; Scan: undefined; + ScanScreen2: undefined; + ScanScreen3: undefined; Info: { peripheral: any }; - Dfu: { deviceId: string; name: string; firmware: string }; + Info2: { peripheral: any }; + Info3: { peripheral: any }; + Spindown: { peripheral: any }; + Dfu: { + deviceId: string; + systemId?: string; + address?: string | number; + name: string; + firmware: string; +}; Privacy: undefined; Setting: undefined; }; @@ -25,6 +57,23 @@ export type RootStackParamList = { const Stack = createNativeStackNavigator(); export default function App() { + + //linshi + React.useEffect(() => { + const test = async () => { + try { + console.log("🔥 App.tsx fetch test start"); + const resp = await fetch("https://www.baidu.com"); + console.log("🔥 App.tsx fetch status =", resp.status); + const text = await resp.text(); + console.log("🔥 App.tsx fetch text length =", text.length); + } catch (e) { + console.log("❌ App.tsx fetch error =", e); + } + }; + + test(); +}, []); return ( + {/* 启动页(无标题) */} - + {/* 首页(标题栏无文字) */} + + + + 我们需要位置权限来扫描附近的蓝牙功率计设备 RCTNewArchEnabled + UIAppFonts + + MaterialCommunityIcons.ttf + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -53,9 +57,5 @@ UIViewControllerBasedStatusBarAppearance - UIAppFonts - - MaterialCommunityIcons.ttf - diff --git a/package-lock.json b/package-lock.json index 939b2c2..b14f42e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,27 +8,34 @@ "name": "dfuapp", "version": "0.0.1", "dependencies": { + "@react-native-async-storage/async-storage": "^2.2.0", "@react-native/codegen": "0.81.4", "@react-native/new-app-screen": "0.81.4", "@react-navigation/native": "^7.1.17", "@react-navigation/native-stack": "^7.3.26", "@systemic-games/react-native-bluetooth-le": "^1.3.0", "@systemic-games/react-native-nordic-nrf5-dfu": "^1.3.0", + "@types/lodash": "^4.17.21", "base-64": "^1.0.0", + "i18next": "^25.7.3", "react": "19.1.0", + "react-i18next": "^16.5.0", "react-native": "0.81.4", + "react-native-device-info": "^15.0.1", "react-native-fs": "^2.20.0", "react-native-safe-area-context": "^5.6.1", + "react-native-safearea-height": "^1.0.8", "react-native-screens": "^4.16.0", + "react-native-toast-message": "^2.3.3", "react-native-vector-icons": "^10.3.0" }, "devDependencies": { "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "20.0.0", - "@react-native-community/cli-platform-android": "20.0.0", - "@react-native-community/cli-platform-ios": "20.0.0", + "@react-native-community/cli": "15.0.0", + "@react-native-community/cli-platform-android": "15.0.0", + "@react-native-community/cli-platform-ios": "15.0.0", "@react-native/babel-preset": "0.81.4", "@react-native/eslint-config": "0.81.4", "@react-native/metro-config": "0.81.4", @@ -2095,14 +2102,14 @@ }, "node_modules/@hapi/hoek": { "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "resolved": "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "resolved": "https://registry.npmmirror.com/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", "devOptional": true, "license": "BSD-3-Clause", @@ -2691,19 +2698,32 @@ "node": ">= 8" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native-community/cli": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.0.0.tgz", - "integrity": "sha512-/cMnGl5V1rqnbElY1Fvga1vfw0d3bnqiJLx2+2oh7l9ulnXfVRWb5tU2kgBqiMxuDOKA+DQoifC9q/tvkj5K2w==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli/-/cli-15.0.0.tgz", + "integrity": "sha512-IzDIFCoWZsoOHLSKcd8OX9gAXnbH83vsyBIFaj/X6praDUA4VCnDf41mGGSOT/VEarGlarTa3tvRcqZ8aE5l/A==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-clean": "20.0.0", - "@react-native-community/cli-config": "20.0.0", - "@react-native-community/cli-doctor": "20.0.0", - "@react-native-community/cli-server-api": "20.0.0", - "@react-native-community/cli-tools": "20.0.0", - "@react-native-community/cli-types": "20.0.0", + "@react-native-community/cli-clean": "15.0.0", + "@react-native-community/cli-config": "15.0.0", + "@react-native-community/cli-debugger-ui": "15.0.0", + "@react-native-community/cli-doctor": "15.0.0", + "@react-native-community/cli-server-api": "15.0.0", + "@react-native-community/cli-tools": "15.0.0", + "@react-native-community/cli-types": "15.0.0", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", @@ -2722,26 +2742,26 @@ } }, "node_modules/@react-native-community/cli-clean": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.0.0.tgz", - "integrity": "sha512-YmdNRcT+Dp8lC7CfxSDIfPMbVPEXVFzBH62VZNbYGxjyakqAvoQUFTYPgM2AyFusAr4wDFbDOsEv88gCDwR3ig==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-clean/-/cli-clean-15.0.0.tgz", + "integrity": "sha512-ndwVj77eYivHTRmwRBmiAhQq0nC012PDr9cqRQ5QUQl9xr9gXlyO26oWA9jJbXNydXf5DHsVqqDVvh97fERsbg==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.0.0", + "@react-native-community/cli-tools": "15.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-glob": "^3.3.2" } }, "node_modules/@react-native-community/cli-config": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.0.0.tgz", - "integrity": "sha512-5Ky9ceYuDqG62VIIpbOmkg8Lybj2fUjf/5wK4UO107uRqejBgNgKsbGnIZgEhREcaSEOkujWrroJ9gweueLfBg==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-config/-/cli-config-15.0.0.tgz", + "integrity": "sha512-YwmQ9Q7JerwqYg0kMD+jwPer1x2ajPR7bjxkOzykfLK4AZxEZo+KgpkSTILMvdqW0WyaXwuYFsgtPa/YVaOn0A==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.0.0", + "@react-native-community/cli-tools": "15.0.0", "chalk": "^4.1.2", "cosmiconfig": "^9.0.0", "deepmerge": "^4.3.0", @@ -2749,44 +2769,28 @@ "joi": "^17.2.1" } }, - "node_modules/@react-native-community/cli-config-android": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.0.0.tgz", - "integrity": "sha512-asv60qYCnL1v0QFWcG9r1zckeFlKG+14GGNyPXY72Eea7RX5Cxdx8Pb6fIPKroWH1HEWjYH9KKHksMSnf9FMKw==", + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-15.0.0.tgz", + "integrity": "sha512-S5A3QZv0ujP/TXZ+1lrlvRfetwuAvrSMJiBEcMh5pzObpr4Ura3naU6bh/ue+QFn9qJtNxoapC2c79B9Ngns/w==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.0.0", - "chalk": "^4.1.2", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.4.1" - } - }, - "node_modules/@react-native-community/cli-config-apple": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.0.0.tgz", - "integrity": "sha512-PS1gNOdpeQ6w7dVu1zi++E+ix2D0ZkGC2SQP6Y/Qp002wG4se56esLXItYiiLrJkhH21P28fXdmYvTEkjSm9/Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "20.0.0", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2" + "serve-static": "^1.13.1" } }, "node_modules/@react-native-community/cli-doctor": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.0.0.tgz", - "integrity": "sha512-cPHspi59+Fy41FDVxt62ZWoicCZ1o34k8LAl64NVSY0lwPl+CEi78jipXJhtfkVqSTetloA8zexa/vSAcJy57Q==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-doctor/-/cli-doctor-15.0.0.tgz", + "integrity": "sha512-UEavoARx1VRxZrNiiVWseP/6dBbP/qAJ9q7S4qf7iT6wstssxi+XCBwoONCQp5IIRJ8LAwKkxCksBuhoMDGzQg==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config": "20.0.0", - "@react-native-community/cli-platform-android": "20.0.0", - "@react-native-community/cli-platform-apple": "20.0.0", - "@react-native-community/cli-platform-ios": "20.0.0", - "@react-native-community/cli-tools": "20.0.0", + "@react-native-community/cli-config": "15.0.0", + "@react-native-community/cli-platform-android": "15.0.0", + "@react-native-community/cli-platform-apple": "15.0.0", + "@react-native-community/cli-platform-ios": "15.0.0", + "@react-native-community/cli-tools": "15.0.0", "chalk": "^4.1.2", "command-exists": "^1.2.8", "deepmerge": "^4.3.0", @@ -2795,14 +2799,25 @@ "node-stream-zip": "^1.9.1", "ora": "^5.4.1", "semver": "^7.5.2", + "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1", "yaml": "^2.2.1" } }, + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -2812,86 +2827,151 @@ "node": ">=10" } }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.0.0.tgz", - "integrity": "sha512-th3ji1GRcV6ACelgC0wJtt9daDZ+63/52KTwL39xXGoqczFjml4qERK90/ppcXU0Ilgq55ANF8Pr+UotQ2AB/A==", + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config-android": "20.0.0", - "@react-native-community/cli-tools": "20.0.0", + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-platform-android/-/cli-platform-android-15.0.0.tgz", + "integrity": "sha512-YQB48ulIdXqe/hEzPmVe5EU13AIQj/PNGZJSqHGoFs4wQYL4jR04iQ7wxIQSuw11TGZO3ne9rG4/rHt+3imE6Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "15.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.4.1", "logkitty": "^0.7.1" } }, "node_modules/@react-native-community/cli-platform-apple": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.0.0.tgz", - "integrity": "sha512-rZZCnAjUHN1XBgiWTAMwEKpbVTO4IHBSecdd1VxJFeTZ7WjmstqA6L/HXcnueBgxrzTCRqvkRIyEQXxC1OfhGw==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-platform-apple/-/cli-platform-apple-15.0.0.tgz", + "integrity": "sha512-DUC4AL3AGNjUDkTrK71fBz2B/aloJm+NHc5deTfEicRvDkyHDM16RqkuFwcvrzaKOtnMDwuDNPM7/PSEp8tbVg==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-config-apple": "20.0.0", - "@react-native-community/cli-tools": "20.0.0", + "@react-native-community/cli-tools": "15.0.0", "chalk": "^4.1.2", "execa": "^5.0.0", - "fast-xml-parser": "^4.4.1" + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.4.1", + "ora": "^5.4.1" } }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.0.0.tgz", - "integrity": "sha512-Z35M+4gUJgtS4WqgpKU9/XYur70nmj3Q65c9USyTq6v/7YJ4VmBkmhC9BticPs6wuQ9Jcv0NyVCY0Wmh6kMMYw==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-15.0.0.tgz", + "integrity": "sha512-2tP9R0tDIEA55ebNoVZFs0fQgz2nrnMy/epmsUrNC2p4+ZmPQEojqjB+OFaZV4Mh0svks+WoPqf9blk39kN7eg==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-platform-apple": "20.0.0" + "@react-native-community/cli-platform-apple": "15.0.0" } }, "node_modules/@react-native-community/cli-server-api": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.0.0.tgz", - "integrity": "sha512-Ves21bXtjUK3tQbtqw/NdzpMW1vR2HvYCkUQ/MXKrJcPjgJnXQpSnTqHXz6ZdBlMbbwLJXOhSPiYzxb5/v4CDg==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-server-api/-/cli-server-api-15.0.0.tgz", + "integrity": "sha512-ypq/5SghbuSaOFVaC+TGAlYCp5hTN0mZ6zBheBzD3OTWXhTu9UCBGCjubmBPLastXr0E6G0djTy4xZ5rwCrHWw==", "devOptional": true, "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "20.0.0", - "body-parser": "^1.20.3", + "@react-native-community/cli-debugger-ui": "15.0.0", + "@react-native-community/cli-tools": "15.0.0", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", "nocache": "^3.0.1", - "open": "^6.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "^26.6.2", "serve-static": "^1.13.1", "ws": "^6.2.3" } }, - "node_modules/@react-native-community/cli-tools": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.0.0.tgz", - "integrity": "sha512-akSZGxr1IajJ8n0YCwQoA3DI0HttJ0WB7M3nVpb0lOM+rJpsBN7WG5Ft+8ozb6HyIPX+O+lLeYazxn5VNG/Xhw==", + "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "version": "15.0.20", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-15.0.20.tgz", + "integrity": "sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@react-native-community/cli-tools": { + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-tools/-/cli-tools-15.0.0.tgz", + "integrity": "sha512-JZzHRJs+6F6or3tloXdbo6aSL2ifbvs7WKsEPjVFuXfaKNEzpQAqWAKMDr95VUEovuX942yD/QRLo6S2W5NTrw==", "devOptional": true, "license": "MIT", "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", "appdirsjs": "^1.2.4", "chalk": "^4.1.2", "execa": "^5.0.0", "find-up": "^5.0.0", - "launch-editor": "^2.9.1", "mime": "^2.4.1", + "open": "^6.2.0", "ora": "^5.4.1", "prompts": "^2.4.2", - "semver": "^7.5.2" + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" } }, "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -2902,9 +2982,9 @@ } }, "node_modules/@react-native-community/cli-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.0.0.tgz", - "integrity": "sha512-7J4hzGWOPTBV1d30Pf2NidV+bfCWpjfCOiGO3HUhz1fH4MvBM0FbbBmE9LE5NnMz7M8XSRSi68ZGYQXgLBB2Qw==", + "version": "15.0.0", + "resolved": "https://registry.npmmirror.com/@react-native-community/cli-types/-/cli-types-15.0.0.tgz", + "integrity": "sha512-sn+h6grsNxJFzKfOdzJX0HOIHbDnWiOo75+T4DBBdREfPTrq0Ao6NybxDWeircdMA6ovYrJLmjByls2MuCQMUA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3367,7 +3447,7 @@ }, "node_modules/@sideway/address": { "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.5.tgz", "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "devOptional": true, "license": "BSD-3-Clause", @@ -3377,14 +3457,14 @@ }, "node_modules/@sideway/formula": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "resolved": "https://registry.npmmirror.com/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "devOptional": true, "license": "BSD-3-Clause" @@ -3545,6 +3625,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", @@ -3828,13 +3914,6 @@ "dev": true, "license": "ISC" }, - "node_modules/@vscode/sudo-prompt": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.1.tgz", - "integrity": "sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3954,7 +4033,7 @@ }, "node_modules/ansi-fragments": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "resolved": "https://registry.npmmirror.com/ansi-fragments/-/ansi-fragments-0.2.1.tgz", "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", "devOptional": true, "license": "MIT", @@ -3966,7 +4045,7 @@ }, "node_modules/ansi-fragments/node_modules/ansi-regex": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "devOptional": true, "license": "MIT", @@ -3976,7 +4055,7 @@ }, "node_modules/ansi-fragments/node_modules/strip-ansi": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "devOptional": true, "license": "MIT", @@ -4026,7 +4105,7 @@ }, "node_modules/appdirsjs": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "resolved": "https://registry.npmmirror.com/appdirsjs/-/appdirsjs-1.2.7.tgz", "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", "devOptional": true, "license": "MIT" @@ -4194,7 +4273,7 @@ }, "node_modules/astral-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "devOptional": true, "license": "MIT", @@ -4413,7 +4492,7 @@ }, "node_modules/base-64": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "resolved": "https://registry.npmmirror.com/base-64/-/base-64-1.0.0.tgz", "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", "license": "MIT" }, @@ -4448,7 +4527,7 @@ }, "node_modules/bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "devOptional": true, "license": "MIT", @@ -4458,48 +4537,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -4566,7 +4603,7 @@ }, "node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "devOptional": true, "funding": [ @@ -4597,7 +4634,7 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "devOptional": true, "license": "MIT", @@ -4628,7 +4665,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4642,7 +4679,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4800,7 +4837,7 @@ }, "node_modules/cli-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "devOptional": true, "license": "MIT", @@ -4813,7 +4850,7 @@ }, "node_modules/cli-spinners": { "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "resolved": "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "devOptional": true, "license": "MIT", @@ -4840,7 +4877,7 @@ }, "node_modules/clone": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "resolved": "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "devOptional": true, "license": "MIT", @@ -4909,14 +4946,14 @@ }, "node_modules/colorette": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "devOptional": true, "license": "MIT" }, "node_modules/command-exists": { "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "resolved": "https://registry.npmmirror.com/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", "devOptional": true, "license": "MIT" @@ -4933,7 +4970,7 @@ }, "node_modules/compressible": { "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "devOptional": true, "license": "MIT", @@ -4946,7 +4983,7 @@ }, "node_modules/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "resolved": "https://registry.npmmirror.com/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "devOptional": true, "license": "MIT", @@ -4965,7 +5002,7 @@ }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "devOptional": true, "license": "MIT", @@ -4975,7 +5012,7 @@ }, "node_modules/compression/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "devOptional": true, "license": "MIT" @@ -5016,16 +5053,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5047,9 +5074,9 @@ } }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5172,9 +5199,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", - "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "version": "1.11.20", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", "devOptional": true, "license": "MIT" }, @@ -5197,7 +5224,7 @@ }, "node_modules/decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "devOptional": true, "license": "MIT", @@ -5248,7 +5275,7 @@ }, "node_modules/defaults": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "resolved": "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "devOptional": true, "license": "MIT", @@ -5364,7 +5391,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5417,7 +5444,7 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "devOptional": true, "license": "MIT", @@ -5426,9 +5453,9 @@ } }, "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "version": "7.21.0", + "resolved": "https://registry.npmmirror.com/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "devOptional": true, "license": "MIT", "bin": { @@ -5458,17 +5485,21 @@ } }, "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/errorhandler/-/errorhandler-1.5.2.tgz", + "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", "devOptional": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "escape-html": "~1.0.3" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/es-abstract": { @@ -5544,7 +5575,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5554,7 +5585,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5592,7 +5623,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6382,9 +6413,9 @@ "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "version": "4.5.5", + "resolved": "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.5.5.tgz", + "integrity": "sha512-cK9c5I/DwIOI7/Q7AlGN3DuTdwN61gwSfL8rvuVPK+0mcCNHHGxRrpiFtaZZRfRMJL3Gl8B2AFlBG6qXf03w9A==", "devOptional": true, "funding": [ { @@ -6394,7 +6425,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^1.1.1" + "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" @@ -6666,7 +6697,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6700,7 +6731,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6855,7 +6886,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6932,7 +6963,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6992,6 +7023,15 @@ "dev": true, "license": "MIT" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -7040,22 +7080,40 @@ "node": ">=10.17.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "devOptional": true, + "node_modules/i18next": { + "version": "25.8.14", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-25.8.14.tgz", + "integrity": "sha512-paMUYkfWJMsWPeE/Hejcw+XLhHrQPehem+4wMo+uELnvIwvCG019L9sAIljwjCmEMtFQQO3YeitJY8Kctei3iA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@babel/runtime": "^7.28.4" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "devOptional": true, "funding": [ @@ -7371,7 +7429,7 @@ }, "node_modules/is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "devOptional": true, "license": "MIT", @@ -7423,7 +7481,7 @@ }, "node_modules/is-interactive": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "resolved": "https://registry.npmmirror.com/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "devOptional": true, "license": "MIT", @@ -7493,6 +7551,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -7607,7 +7674,7 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "devOptional": true, "license": "MIT", @@ -7666,7 +7733,7 @@ }, "node_modules/is-wsl": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", "devOptional": true, "license": "MIT", @@ -8376,7 +8443,7 @@ }, "node_modules/joi": { "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "resolved": "https://registry.npmmirror.com/joi/-/joi-17.13.3.tgz", "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "devOptional": true, "license": "BSD-3-Clause", @@ -8511,17 +8578,6 @@ "node": ">=6" } }, - "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8622,7 +8678,7 @@ }, "node_modules/log-symbols": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "devOptional": true, "license": "MIT", @@ -8639,7 +8695,7 @@ }, "node_modules/logkitty": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "resolved": "https://registry.npmmirror.com/logkitty/-/logkitty-0.7.1.tgz", "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", "devOptional": true, "license": "MIT", @@ -8654,7 +8710,7 @@ }, "node_modules/logkitty/node_modules/cliui": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "devOptional": true, "license": "ISC", @@ -8666,7 +8722,7 @@ }, "node_modules/logkitty/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "devOptional": true, "license": "MIT", @@ -8680,7 +8736,7 @@ }, "node_modules/logkitty/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "devOptional": true, "license": "MIT", @@ -8693,7 +8749,7 @@ }, "node_modules/logkitty/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "devOptional": true, "license": "MIT", @@ -8709,7 +8765,7 @@ }, "node_modules/logkitty/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "devOptional": true, "license": "MIT", @@ -8722,7 +8778,7 @@ }, "node_modules/logkitty/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "devOptional": true, "license": "MIT", @@ -8737,14 +8793,14 @@ }, "node_modules/logkitty/node_modules/y18n": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "devOptional": true, "license": "ISC" }, "node_modules/logkitty/node_modules/yargs": { "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "devOptional": true, "license": "MIT", @@ -8767,7 +8823,7 @@ }, "node_modules/logkitty/node_modules/yargs-parser": { "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "devOptional": true, "license": "ISC", @@ -8848,28 +8904,30 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9254,7 +9312,7 @@ }, "node_modules/mime": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "devOptional": true, "license": "MIT", @@ -9267,7 +9325,7 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "devOptional": true, "license": "MIT", @@ -9367,7 +9425,7 @@ }, "node_modules/negotiator": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "devOptional": true, "license": "MIT", @@ -9377,7 +9435,7 @@ }, "node_modules/nocache": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "resolved": "https://registry.npmmirror.com/nocache/-/nocache-3.0.4.tgz", "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", "devOptional": true, "license": "MIT", @@ -9399,7 +9457,7 @@ }, "node_modules/node-stream-zip": { "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "resolved": "https://registry.npmmirror.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", "devOptional": true, "license": "MIT", @@ -9464,7 +9522,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9572,7 +9630,7 @@ }, "node_modules/on-headers": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "devOptional": true, "license": "MIT", @@ -9607,7 +9665,7 @@ }, "node_modules/open": { "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "resolved": "https://registry.npmmirror.com/open/-/open-6.4.0.tgz", "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", "devOptional": true, "license": "MIT", @@ -9638,7 +9696,7 @@ }, "node_modules/ora": { "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "resolved": "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "devOptional": true, "license": "MIT", @@ -10030,22 +10088,6 @@ ], "license": "MIT" }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "devOptional": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -10103,22 +10145,6 @@ "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -10171,6 +10197,33 @@ "react": ">=17.0.0" } }, + "node_modules/react-i18next": { + "version": "16.5.6", + "resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-16.5.6.tgz", + "integrity": "sha512-Ua7V2/efA88ido7KyK51fb8Ki8M/sRfW8LR/rZ/9ZKr2luhuTI7kwYZN5agT1rWG7aYm5G0RYE/6JR8KJoCMDw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -10234,6 +10287,15 @@ } } }, + "node_modules/react-native-device-info": { + "version": "15.0.2", + "resolved": "https://registry.npmmirror.com/react-native-device-info/-/react-native-device-info-15.0.2.tgz", + "integrity": "sha512-dd71eXG2l3Cwp66IvKNadMTB8fhU3PEjyVddI97sYan+D4bgIAUmgGDhbSOFvHcGavksb2U17kiQYaDiK2WK2g==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-fs": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz", @@ -10278,6 +10340,15 @@ "react-native": "*" } }, + "node_modules/react-native-safearea-height": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/react-native-safearea-height/-/react-native-safearea-height-1.0.8.tgz", + "integrity": "sha512-nAACsA6HwbMI5zUKHUy4IOvqrohA2AFYB31Yhv6JOXbok6S7VOjOWJZNJx4dllECREon254Y2ZlqeVGUb2eqgQ==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-native-screens": { "version": "4.16.0", "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", @@ -10293,6 +10364,16 @@ "react-native": "*" } }, + "node_modules/react-native-toast-message": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz", + "integrity": "sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-vector-icons": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz", @@ -10401,7 +10482,7 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "devOptional": true, "license": "MIT", @@ -10552,7 +10633,7 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "devOptional": true, "license": "ISC" @@ -10623,7 +10704,7 @@ }, "node_modules/restore-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "devOptional": true, "license": "MIT", @@ -10708,7 +10789,7 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "devOptional": true, "funding": [ @@ -10762,13 +10843,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -10879,7 +10953,7 @@ }, "node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "devOptional": true, "license": "ISC" @@ -10978,7 +11052,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -10998,7 +11072,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -11015,7 +11089,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11034,7 +11108,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11089,7 +11163,7 @@ }, "node_modules/slice-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "devOptional": true, "license": "MIT", @@ -11104,7 +11178,7 @@ }, "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "devOptional": true, "license": "MIT", @@ -11117,7 +11191,7 @@ }, "node_modules/slice-ansi/node_modules/color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "devOptional": true, "license": "MIT", @@ -11127,7 +11201,7 @@ }, "node_modules/slice-ansi/node_modules/color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "devOptional": true, "license": "MIT" @@ -11249,7 +11323,7 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "devOptional": true, "license": "MIT", @@ -11446,7 +11520,7 @@ }, "node_modules/strnum": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "resolved": "https://registry.npmmirror.com/strnum/-/strnum-1.1.2.tgz", "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", "devOptional": true, "funding": [ @@ -11457,6 +11531,14 @@ ], "license": "MIT" }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmmirror.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "devOptional": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11663,20 +11745,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -11759,7 +11827,7 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11907,9 +11975,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -11923,7 +11991,7 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "devOptional": true, "license": "MIT" @@ -11954,7 +12022,7 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "devOptional": true, "license": "MIT", @@ -11968,6 +12036,15 @@ "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", "license": "MIT" }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -11985,7 +12062,7 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "resolved": "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "devOptional": true, "license": "MIT", @@ -12084,7 +12161,7 @@ }, "node_modules/which-module": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "devOptional": true, "license": "ISC" diff --git a/package.json b/package.json index db6986c..149cc7c 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,9 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "20.0.0", - "@react-native-community/cli-platform-android": "20.0.0", - "@react-native-community/cli-platform-ios": "20.0.0", + "@react-native-community/cli": "15.0.0", + "@react-native-community/cli-platform-android": "15.0.0", + "@react-native-community/cli-platform-ios": "15.0.0", "@react-native/babel-preset": "0.81.4", "@react-native/eslint-config": "0.81.4", "@react-native/metro-config": "0.81.4", @@ -53,7 +53,10 @@ "prettier": "2.8.8", "react-test-renderer": "19.1.0", "typescript": "^5.8.3" + }, + + "engines": { "node": ">=20" } diff --git a/src/DfuScreen.tsx b/src/DfuScreen.tsx index 25b8c6a..4a9d83b 100644 --- a/src/DfuScreen.tsx +++ b/src/DfuScreen.tsx @@ -1,10 +1,16 @@ import React, { useEffect, useState } from "react"; -import { View, Text, StyleSheet, Alert, BackHandler } from "react-native"; +import { View, Text, StyleSheet, Alert, Platform } from "react-native"; import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { RootStackParamList } from "../App"; import RNFS from "react-native-fs"; -import { startDfu, DfuProgressEvent, DfuStateEvent } from "@systemic-games/react-native-nordic-nrf5-dfu"; -import { useTranslation } from 'react-i18next'; +import { + startDfu, + getDfuTargetId, + DfuProgressEvent, + DfuStateEvent, +} from "@systemic-games/react-native-nordic-nrf5-dfu"; +import { useTranslation } from "react-i18next"; +import { encode as btoa } from "base-64"; import MyStatusbar from "./component/MyStatusbar"; import MyHeader from "./component/MyHeader"; @@ -16,162 +22,337 @@ interface DeviceInfo { download: string; } +interface ParsedFirmware { + hardware: number; + iteration: number; + build: string; + raw: string; +} + +const bytesToBase64 = (bytes: Uint8Array): string => { + let binary = ""; + const chunkSize = 0x8000; + + for (let i = 0; i < bytes.length; i += chunkSize) { + const chunk = bytes.subarray(i, i + chunkSize); + binary += String.fromCharCode(...chunk); + } + + return btoa(binary); +}; + +const parseFirmwareVersion = (text: string): ParsedFirmware => { + const raw = String(text ?? "").trim(); + const parts = raw.split("."); + + if (parts.length < 2) { + throw new Error(`固件版本格式不正确: ${raw}`); + } + + const hardware = parseInt(parts[0], 10); + const iteration = parseInt(parts[1], 10); + const build = parts.length >= 3 ? parts[2] : ""; + + if (Number.isNaN(hardware) || Number.isNaN(iteration)) { + throw new Error(`固件版本无法解析: ${raw}`); + } + + return { + hardware, + iteration, + build, + raw, + }; +}; + export default function DfuScreen({ route, navigation }: Props) { const { t } = useTranslation(); - const { deviceId, name, firmware: deviceFirmware } = route.params; + const { + deviceId, + systemId, + address, + name, + firmware: deviceFirmware, + } = route.params; const [progress, setProgress] = useState(0); - const [state, setState] = useState(t('dfu.preparing')); + const [state, setState] = useState("准备中..."); const [error, setError] = useState(); - const [latestVersion, setLatestVersion] = useState(t('dfu.reading')); + const [latestVersion, setLatestVersion] = useState("读取中..."); const [isDfuRunning, setIsDfuRunning] = useState(false); - const mapDfuStateToChinese = (state: string): string => { - switch (state) { - case "connecting": return t('dfu.stateConnecting'); - case "starting": return t('dfu.stateStarting'); - case "enablingDfuMode": return t('dfu.stateEnablingDfuMode'); - case "uploading": return t('dfu.stateUploading'); - case "validating": return t('dfu.stateValidating'); - case "disconnecting": return t('dfu.stateDisconnecting'); - case "completed": return t('dfu.stateCompleted'); - case "aborted": return t('dfu.stateAborted'); + const mapDfuStateToChinese = (s: string): string => { + switch (s) { + case "connecting": + return "连接中…"; + case "starting": + return "初始化中…"; + case "enablingDfuMode": + return "启用 DFU 模式…"; + case "uploading": + return "上传固件中…"; + case "validating": + return "校验固件…"; + case "disconnecting": + return "断开连接…"; + case "completed": + return "升级完成"; + case "aborted": + return "已取消"; case "failed": - case "dfu_failed": return t('dfu.stateFailed'); - case "initializing": return t('dfu.stateInitializing'); - case "errored": return t('dfu.stateErrored'); - default: return state; + case "dfu_failed": + return "升级失败"; + case "initializing": + return "启动中…"; + case "errored": + return "升级出错!"; + default: + return s; } }; - - // ✅ 拦截所有导航返回(iOS + Android) useEffect(() => { const unsubscribe = navigation.addListener("beforeRemove", (e) => { if (!isDfuRunning) return; - - // 阻止返回 e.preventDefault(); - Alert.alert(t('dfu.pleaseWait'), t('dfu.doNotReturn')); + Alert.alert("请稍候", "正在升级,请勿返回或关闭应用!"); }); return unsubscribe; }, [navigation, isDfuRunning]); - - - useEffect(() => { const runDfu = async () => { try { setIsDfuRunning(true); + setError(undefined); - const manifestUrl = "https://powerfun.oss-cn-shanghai.aliyuncs.com/yecongdfu/latest.json"; + const rawDeviceId = String(deviceId ?? "").trim(); + const rawSystemId = String(systemId ?? rawDeviceId).trim(); + const rawAddress = + typeof address === "number" + ? address + : address !== undefined && address !== null + ? Number(address) + : undefined; + + const safeDeviceId = getDfuTargetId({ + systemId: rawSystemId, + address: rawAddress, + }); + + const firmwareText = String(deviceFirmware ?? "").trim(); + + console.log("🔥 rawDeviceId =", rawDeviceId); + console.log("🔥 rawSystemId =", rawSystemId); + console.log("🔥 rawAddress =", rawAddress); + console.log("🔥 safeDeviceId =", safeDeviceId); + console.log("🔥 firmwareText =", JSON.stringify(firmwareText)); + + if (!safeDeviceId) { + throw new Error("无法生成 DFU 目标设备 ID"); + } + + const currentFw = parseFirmwareVersion(firmwareText); + console.log("🔥 currentFw =", currentFw); + + const manifestUrl = + "https://powerfun.oss-cn-shanghai.aliyuncs.com/yecongdfu/latest.json"; const manifestPath = RNFS.CachesDirectoryPath + "/latest.json"; - await RNFS.downloadFile({ fromUrl: manifestUrl, toFile: manifestPath }).promise; - const manifestContent = await RNFS.readFile(manifestPath); - const manifest = JSON.parse(manifestContent) as { devices: DeviceInfo[] }; + console.log("🔥 before fetch manifest"); + const manifestResp = await fetch(manifestUrl); + console.log("🔥 manifest status =", manifestResp.status); - const [deviceHWStr, deviceFWStr] = deviceFirmware.split("."); - const deviceHW = parseInt(deviceHWStr); - const deviceFW = parseInt(deviceFWStr); + if (!manifestResp.ok) { + throw new Error(`manifest 下载失败,HTTP ${manifestResp.status}`); + } + + const manifestText = await manifestResp.text(); + console.log("🔥 manifest text =", manifestText); + + await RNFS.writeFile(manifestPath, manifestText, "utf8"); + console.log("🔥 manifest saved =", manifestPath); + + let manifest: { devices: DeviceInfo[] }; + + try { + manifest = JSON.parse(manifestText) as { devices: DeviceInfo[] }; + } catch (e) { + throw new Error("manifest 不是合法 JSON: " + manifestText.slice(0, 200)); + } + + const deviceInfo = manifest.devices.find( + (d) => d.hardware === currentFw.hardware + ); + + console.log("🔥 matched deviceInfo =", deviceInfo); - const deviceInfo = manifest.devices.find(d => d.hardware === deviceHW); if (!deviceInfo) { setIsDfuRunning(false); - Alert.alert(t('dfu.cannotUpgrade'), t('dfu.hardwareNotFound', { hardware: deviceHW }), [ - { text: t('dfu.confirm'), onPress: () => navigation.goBack() }, - ]); + Alert.alert( + "无法升级", + `未找到 hardware=${currentFw.hardware} 的固件`, + [{ text: "确认", onPress: () => navigation.goBack() }] + ); return; } setLatestVersion(deviceInfo.latestFirmware); - const [, latestFWStr] = deviceInfo.latestFirmware.split("."); - const latestFW = parseInt(latestFWStr); + const latestFw = parseFirmwareVersion(deviceInfo.latestFirmware); + console.log("🔥 latestFw =", latestFw); - if (latestFW <= deviceFW) { + if (latestFw.hardware !== currentFw.hardware) { + throw new Error( + `服务器固件硬件号不匹配:当前 ${currentFw.hardware},服务器 ${latestFw.hardware}` + ); + } + + if (latestFw.iteration <= currentFw.iteration) { setIsDfuRunning(false); - Alert.alert(t('dfu.noNeedUpgrade'), t('dfu.alreadyLatest'), [ - { text: t('dfu.confirm'), onPress: () => navigation.goBack() }, + Alert.alert("无需升级", "已是最新固件,无需升级", [ + { text: "确认", onPress: () => navigation.goBack() }, ]); return; } - const localPath = RNFS.CachesDirectoryPath + "/firmware.zip"; - await RNFS.downloadFile({ fromUrl: deviceInfo.download, toFile: localPath }).promise; + console.log("🔥 upgrade allowed", { + currentIteration: currentFw.iteration, + latestIteration: latestFw.iteration, + }); - await startDfu(deviceId, "file://" + localPath, { - dfuStateListener: (ev: DfuStateEvent) => setState(ev.state), - dfuProgressListener: (ev: DfuProgressEvent) => setProgress(ev.percent), + const zipResp = await fetch(deviceInfo.download); + console.log("🔥 zip status =", zipResp.status); + + if (!zipResp.ok) { + throw new Error(`固件包下载失败,HTTP ${zipResp.status}`); + } + + const zipArrayBuffer = await zipResp.arrayBuffer(); + const zipBytes = new Uint8Array(zipArrayBuffer); + console.log("🔥 zip bytes =", zipBytes.length); + + const zipBase64 = bytesToBase64(zipBytes); + const localPath = RNFS.CachesDirectoryPath + "/firmware.zip"; + + await RNFS.writeFile(localPath, zipBase64, "base64"); + console.log("🔥 zip saved =", localPath); + + const dfuFilePath = + Platform.OS === "android" ? "file://" + localPath : localPath; + + console.log("🔥 before startDfu =", { + safeDeviceId, + dfuFilePath, + currentFirmware: currentFw.raw, + latestFirmware: latestFw.raw, + }); + + await startDfu(safeDeviceId, dfuFilePath, { + dfuStateListener: (ev: DfuStateEvent) => { + console.log("🔥 dfu state =", ev.state); + setState(ev.state); + }, + dfuProgressListener: (ev: DfuProgressEvent) => { + console.log("🔥 dfu progress =", ev.percent); + setProgress(ev.percent); + }, }); setIsDfuRunning(false); - Alert.alert(t('dfu.upgradeSuccess'), t('dfu.upgradeSuccessMessage'), [ - { text: t('dfu.confirm'), onPress: () => navigation.navigate("Home") }, - ]); + Alert.alert("升级成功", "升级成功,请重连设备", [ + { + text: "确认", + onPress: () => { + navigation.reset({ + index: 0, + routes: [{ name: "Home" }], + }); + }, + }, +]); } catch (err: any) { + console.log("❌ runDfu error =", err); setIsDfuRunning(false); - setError(err.message || t('dfu.dfuFailed')); - Alert.alert(t('dfu.upgradeFailed'), err.message || t('dfu.dfuFailed'), [ - { text: t('dfu.confirm'), onPress: () => navigation.goBack() }, + setError(err?.message || "DFU失败"); + Alert.alert("升级失败", err?.message || "DFU失败", [ + { text: "确认", onPress: () => navigation.goBack() }, ]); } }; runDfu(); - }, [deviceId, deviceFirmware, navigation]); + }, [deviceId, systemId, address, deviceFirmware, navigation]); return ( - - - - + + + + + + + 蓝牙名称: {name || "--"} + - - {t('dfu.bluetoothName')}: {name} - + 最新版本: {latestVersion} + - - {t('dfu.latestVersion')}: {latestVersion} - + 当前版本: {deviceFirmware || "--"} + - - {t('dfu.currentVersion')}: {deviceFirmware} - - - - - {t('dfu.upgradeStatus')}: {mapDfuStateToChinese(state)} + + 升级状态: {mapDfuStateToChinese(state)} - {/* 横向进度条 */} {progress}% - {error && {error}} + {!!error && {error}} ); } const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#FFFFFF", + }, + content: { + flex: 1, + padding: 20, + backgroundColor: "#FFFFFF", + }, row: { borderBottomWidth: 1, - borderBottomColor: "red", - paddingBottom: 4, - marginBottom: 8, + borderBottomColor: "#E7141E", + paddingBottom: 6, + marginBottom: 12, + }, + titleText: { + fontSize: 18, + color: "#111111", + fontWeight: "600", + }, + normalText: { + fontSize: 16, + color: "#222222", }, progressContainer: { height: 30, - backgroundColor: "#eee", + backgroundColor: "#EEEEEE", borderRadius: 15, overflow: "hidden", marginTop: 40, @@ -185,6 +366,11 @@ const styles = StyleSheet.create({ position: "absolute", alignSelf: "center", fontWeight: "bold", - color: "#000", + color: "#000000", }, -}); + errorText: { + color: "red", + marginTop: 20, + fontSize: 14, + }, +}); \ No newline at end of file diff --git a/src/HomeScreen.tsx b/src/HomeScreen.tsx index 2b35f3a..92d9dc6 100644 --- a/src/HomeScreen.tsx +++ b/src/HomeScreen.tsx @@ -28,7 +28,19 @@ export default function HomeScreen({ navigation }: Props) { style={styles.button} onPress={() => navigation.navigate("Scan")} > - {t('home.scan')} + {t("home.powerMeter")} + + navigation.navigate("ScanScreen2")} + > + {t("home.paddle")} + + navigation.navigate("ScanScreen3")} + > + {t("home.T5trainer")} diff --git a/src/InfoScreen.tsx b/src/InfoScreen.tsx index 42352b8..a82a254 100644 --- a/src/InfoScreen.tsx +++ b/src/InfoScreen.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState, useCallback, useRef } from "react"; +// InfoScreen.tsx此页面为功率计信息页面 +import React, { useEffect, useState, useRef } from "react"; import { View, Text as RNText, @@ -6,7 +7,6 @@ import { ScrollView, StyleSheet, ActivityIndicator, - useColorScheme, Alert, Pressable, } from "react-native"; @@ -57,6 +57,7 @@ export default function InfoScreen({ route, navigation }: Props) { const [rightbalance, setRightBalance] = useState(0); const [isConnected, setIsConnected] = useState(false); + const [isDeviceReady, setIsDeviceReady] = useState(false); const [isLoading, setIsLoading] = useState(true); const [serial, setSerial] = useState(t('info.reading')); const [firmware, setFirmware] = useState(t('info.reading')); @@ -72,6 +73,12 @@ export default function InfoScreen({ route, navigation }: Props) { const notifySubscribedRef = useRef(false); const disconnectingRef = useRef(false); + const mountedRef = useRef(true); + const deviceReadyRef = useRef(false); + const readyResolverRef = useRef<(() => void) | null>(null); + const readSuccessTimeoutRef = useRef | null>(null); + const powerDataSubscribedRef = useRef(false); + const initialInfoLoadedRef = useRef(false); const prevConnectedRef = useRef(isConnected); const isActiveRef = useRef(true); @@ -83,6 +90,227 @@ export default function InfoScreen({ route, navigation }: Props) { const calibrationTimeoutRef = useRef(null); const isCalibratingRef = useRef(false); + const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + + const toUint8Array = (raw: unknown): Uint8Array | null => { + if (!raw) return null; + if (raw instanceof Uint8Array) return raw; + if (raw instanceof ArrayBuffer) return new Uint8Array(raw); + if (Array.isArray(raw)) return new Uint8Array(raw); + + if ( + typeof raw === "object" && + raw !== null && + "buffer" in raw && + (raw as { buffer?: unknown }).buffer instanceof ArrayBuffer + ) { + const view = raw as { + buffer: ArrayBuffer; + byteOffset?: number; + byteLength?: number; + }; + return new Uint8Array( + view.buffer, + view.byteOffset || 0, + view.byteLength + ); + } + + return null; + }; + + const decodeTextValue = (raw: unknown) => { + const bytes = toUint8Array(raw); + if (!bytes || bytes.length === 0) return null; + + const text = String.fromCharCode(...Array.from(bytes)) + .replace(/\0/g, "") + .trim(); + + return text || null; + }; + + const waitForDeviceReady = (timeoutMs = 6000) => + new Promise((resolve, reject) => { + if (deviceReadyRef.current) { + resolve(); + return; + } + + const timer = setTimeout(() => { + if (readyResolverRef.current === onReady) { + readyResolverRef.current = null; + } + reject(new Error("device ready timeout")); + }, timeoutMs); + + const onReady = () => { + clearTimeout(timer); + if (readyResolverRef.current === onReady) { + readyResolverRef.current = null; + } + resolve(); + }; + + readyResolverRef.current = onReady; + }); + + const readCharacteristicWithRetry = async ( + serviceUuid: string, + characteristicUuid: string, + attempts = 3, + delayMs = 180 + ) => { + let lastError: unknown; + + for (let attempt = 0; attempt < attempts; attempt += 1) { + try { + const value = await Central.readCharacteristic( + peripheral, + fullUUID(serviceUuid), + fullUUID(characteristicUuid) + ); + const bytes = toUint8Array(value); + + if (bytes && bytes.length > 0) { + return bytes; + } + + lastError = new Error("empty characteristic value"); + } catch (err) { + lastError = err; + } + + if (attempt < attempts - 1) { + await sleep(delayMs); + } + } + + throw lastError ?? new Error("read characteristic failed"); + }; + + const readTextCharacteristic = async (serviceUuid: string, characteristicUuid: string) => { + for (let attempt = 0; attempt < 3; attempt += 1) { + try { + const bytes = await readCharacteristicWithRetry( + serviceUuid, + characteristicUuid, + 1 + ); + const text = decodeTextValue(bytes); + + if (text) { + return text; + } + } catch { + // Continue retrying with a short gap below. + } + + if (attempt < 2) { + await sleep(180); + } + } + + return "未知"; + }; + + const readBatteryCharacteristic = async () => { + for (let attempt = 0; attempt < 3; attempt += 1) { + try { + const bytes = await readCharacteristicWithRetry("180f", "2a19", 1); + const batteryValue = bytes[0]; + + if (Number.isInteger(batteryValue) && batteryValue >= 0 && batteryValue <= 100) { + return `${batteryValue}%`; + } + } catch { + // Continue retrying with a short gap below. + } + + if (attempt < 2) { + await sleep(180); + } + } + + return "未知"; + }; + + const subscribePowerDataIfNeeded = async () => { + if (!deviceReadyRef.current || !initialInfoLoadedRef.current || powerDataSubscribedRef.current) { + return; + } + + console.log("✅ 订阅 2A63 特性通知..."); + + await Central.unsubscribeCharacteristic( + peripheral, + fullUUID("1818"), + fullUUID("2a63") + ).catch(() => {}); + + await Central.subscribeCharacteristic( + peripheral, + fullUUID("1818"), + fullUUID("2a63"), + (notifyEv) => { + try { + const byteArray = toUint8Array(notifyEv.value); + if (!byteArray) return; + parseData(byteArray); + } catch (err) { + console.warn("❌ 处理通知数据失败", err); + } + } + ); + + powerDataSubscribedRef.current = true; + console.log("✅ 已订阅 2A63 特性通知"); + }; + + const subscribeFff3IfNeeded = async () => { + if (notifySubscribedRef.current) return; + + console.log("✅ device ready - subscribe notify FFF3"); + await Central.unsubscribeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerNotifyUuid) + ).catch(() => {}); + + await Central.subscribeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerNotifyUuid), + (notifyEv) => { + try { + const raw = (notifyEv as any).value; + const byteArray = toUint8Array(raw); + if (!byteArray) return; + if (byteArray[0] === 0x02 && byteArray.length >= 2) { + const rawTrim = + byteArray.length >= 3 + ? byteArray[1] | (byteArray[2] << 8) + : byteArray[1]; + const displayTrim = + byteArray.length >= 3 + ? (rawTrim / 100).toFixed(2).replace(/\.?0+$/, "") + : rawTrim.toString(); + + setPowerTrim(displayTrim); + } else if (byteArray[0] === 0x05 && byteArray.length >= 3) { + handleFFF3Response(byteArray); + } + } catch (err) { + console.warn("notify parse error", err); + } + } + ); + + notifySubscribedRef.current = true; + console.log("✅ notify 已订阅"); + }; + useFocusEffect( React.useCallback(() => { // 页面获得焦点 @@ -145,49 +373,40 @@ export default function InfoScreen({ route, navigation }: Props) { if (addr !== deviceKey) return; console.log("🔌 connection event:", ev.connectionStatus); - setIsConnected( - ev.connectionStatus === "connected" || ev.connectionStatus === "ready" - ); + const connected = + ev.connectionStatus === "connected" || ev.connectionStatus === "ready"; + const isReady = ev.connectionStatus === "ready"; - if (ev.connectionStatus === "ready" && !notifySubscribedRef.current) { - console.log("✅ device ready - subscribe notify FFF3"); + setIsConnected(connected); + setIsDeviceReady(isReady); + deviceReadyRef.current = isReady; + + if (!isReady) { + notifySubscribedRef.current = false; + powerDataSubscribedRef.current = false; + } + + if (isReady && readyResolverRef.current) { + const resolveReady = readyResolverRef.current; + readyResolverRef.current = null; + resolveReady(); + } + + if (isReady && !notifySubscribedRef.current) { try { - await Central.unsubscribeCharacteristic( - peripheral, - fullUUID(powerServiceUuid), - fullUUID(powerNotifyUuid) - ).catch(() => { }); - await Central.subscribeCharacteristic( - peripheral, - fullUUID(powerServiceUuid), - fullUUID(powerNotifyUuid), - (notifyEv) => { - try { - const raw = (notifyEv as any).value; - if (!raw) return; - const byteArray = raw instanceof Uint8Array - ? raw - : new Uint8Array(raw); - if (byteArray[0] === 0x02 && byteArray.length >= 2) { - // 功率微调数据 - setPowerTrim(byteArray[1].toString()); - //setInputTrim(byteArray[1].toString()); - } - else if (byteArray[0] === 0x05 && byteArray.length >= 3) { - // 校准响应数据 (05XXXX格式) - handleFFF3Response(byteArray); - } - } catch (err) { - console.warn("notify parse error", err); - } - } - ); - notifySubscribedRef.current = true; - console.log("✅ notify 已订阅"); + await subscribeFff3IfNeeded(); } catch (err) { console.warn("❌ notify 订阅失败:", err); } } + + if (isReady && initialInfoLoadedRef.current && !powerDataSubscribedRef.current) { + try { + await subscribePowerDataIfNeeded(); + } catch (err) { + console.warn("❌ 实时数据订阅失败:", err); + } + } }; @@ -201,45 +420,44 @@ export default function InfoScreen({ route, navigation }: Props) { // ========== 首次连接并读取信息 ========== useEffect(() => { + let cancelled = false; + (async () => { setIsLoading(true); + let shouldShowReadSuccess = false; try { await Central.connectPeripheral(peripheral); - await new Promise((resolve) => setTimeout(resolve, 500)); + await waitForDeviceReady(6000); + await sleep(150); + await subscribeFff3IfNeeded(); + await sleep(120); - const readStr = async (srv: string, char: string) => { - try { - const v = await Central.readCharacteristic( - peripheral, - fullUUID(srv), - fullUUID(char) - ); - return v ? String.fromCharCode(...(v as any)) : "未知"; - } catch { - return "未知"; - } - }; + const serialValue = await readTextCharacteristic("180a", "2a25"); + const firmwareValue = await readTextCharacteristic("180a", "2a28"); + const hardwareValue = await readTextCharacteristic("180a", "2a27"); + const batteryValue = await readBatteryCharacteristic(); - setSerial(await readStr("180a", "2a25")); - setFirmware(await readStr("180a", "2a28")); - setHardware(await readStr("180a", "2a27")); - - try { - const v = await Central.readCharacteristic( - peripheral, - fullUUID("180f"), - fullUUID("2a19") - ); - if (v && (v as any).length) setBattery(`${(v as any)[0]}%`); - } catch { - setBattery("未知"); + if (!cancelled && mountedRef.current) { + setSerial(serialValue); + setFirmware(firmwareValue); + setHardware(hardwareValue); + setBattery(batteryValue); + initialInfoLoadedRef.current = true; } + await sleep(120); + await subscribePowerDataIfNeeded(); + + shouldShowReadSuccess = + serialValue !== "未知" || + firmwareValue !== "未知" || + hardwareValue !== "未知" || + batteryValue !== "未知"; + console.log("✅ info 读取完成"); - - try { + await sleep(150); await Central.writeCharacteristic( peripheral, fullUUID(powerServiceUuid), @@ -251,22 +469,35 @@ export default function InfoScreen({ route, navigation }: Props) { // notify 回调里已经订阅了,所以这里不用再重复订阅 // 可以稍等 300ms,确保 notify 回来后 UI 会更新 - await new Promise(resolve => setTimeout(() => resolve(), 300)); + await sleep(300); } catch (err) { console.warn("❌ 首次读取功率微调失败", err); } - } catch (e) { console.warn("❌ 读取失败", e); } finally { - setIsLoading(false); - - // ✅ 显示读取成功提示 2 秒 - setReadSuccessToast(true); - setTimeout(() => setReadSuccessToast(false), 2000); + if (!cancelled && mountedRef.current) { + setIsLoading(false); + } + if (!cancelled && mountedRef.current && shouldShowReadSuccess) { + setReadSuccessToast(true); + if (readSuccessTimeoutRef.current) { + clearTimeout(readSuccessTimeoutRef.current); + } + readSuccessTimeoutRef.current = setTimeout(() => { + if (mountedRef.current) { + setReadSuccessToast(false); + } + }, 2000); + } } })(); + + return () => { + cancelled = true; + initialInfoLoadedRef.current = false; + }; }, [peripheral]); @@ -274,59 +505,19 @@ export default function InfoScreen({ route, navigation }: Props) { // ========== 订阅 1818 服务的 2A63 特性 (通知) ========== useEffect(() => { - const subscribeToPowerData = async () => { - try { - // 确保设备已经连接 - if (!isConnected) return; - - console.log("✅ 订阅 2A63 特性通知..."); - - // 先取消之前的订阅(防止重复订阅) - await Central.unsubscribeCharacteristic( - peripheral, - fullUUID("1818"), - fullUUID("2a63") - ).catch(() => { }); - - // 订阅 2A63 特性通知 - await Central.subscribeCharacteristic( - peripheral, - fullUUID("1818"), - fullUUID("2a63"), - (notifyEv) => { - try { - const raw = notifyEv.value; - if (raw) { - // 将 ArrayBuffer 转换为字节数组 - const byteArray = new Uint8Array(raw); - - // 解析数据 - parseData(byteArray); - } - } catch (err) { - console.warn("❌ 处理通知数据失败", err); - } - } - ); - - console.log("✅ 已订阅 2A63 特性通知"); - } catch (err) { + if (peripheral && deviceReadyRef.current && initialInfoLoadedRef.current) { + subscribePowerDataIfNeeded().catch((err) => { console.warn("❌ 订阅 2A63 特性失败", err); - } - }; - - // 只在设备已连接时进行订阅 - if (peripheral && isConnected) { - subscribeToPowerData(); + }); } - // 清理订阅(当组件卸载或者连接状态变化时) return () => { - if (peripheral && isConnected) { + if (peripheral && powerDataSubscribedRef.current) { + powerDataSubscribedRef.current = false; Central.unsubscribeCharacteristic(peripheral, fullUUID("1818"), fullUUID("2a63")).catch(() => { }); } }; - }, [peripheral, isConnected]); + }, [peripheral, isDeviceReady]); const cadenceStateRef = useRef({ lastCadenceCount: 0, // 上一次的踏频翻转次数,用于计算差值 @@ -442,7 +633,7 @@ export default function InfoScreen({ route, navigation }: Props) { // ========== 写入功率微调 ========== const updatePowerTrim = async () => { - const val = parseInt(inputTrim); + const val = Number(inputTrim); if (isNaN(val) || val < 50 || val > 200) { Alert.alert(t('info.disconnectTitle'), t('info.trimRangeAlert')); return; @@ -458,13 +649,16 @@ export default function InfoScreen({ route, navigation }: Props) { try { setPowerTrimLoading(true); + const scaledVal = Math.round(val * 100); + const low = scaledVal & 0xff; + const high = (scaledVal >> 8) & 0xff; - console.log("🚀 写入功率微调", val); + console.log("🚀 写入功率微调", val, "=>", scaledVal); await Central.writeCharacteristic( peripheral, fullUUID(powerServiceUuid), fullUUID(powerWriteUuid), - new Uint8Array([0x02, val]).buffer, + new Uint8Array([0x02, low, high]).buffer, { withoutResponse: false } ); await new Promise((resolve) => setTimeout(() => resolve(), 150)); @@ -483,6 +677,8 @@ export default function InfoScreen({ route, navigation }: Props) { } catch (err) { console.warn("❌ 写入失败", err); Alert.alert(t('info.writeFailed')); + } finally { + setPowerTrimLoading(false); } }; @@ -584,12 +780,22 @@ export default function InfoScreen({ route, navigation }: Props) { }; useEffect(() => { + mountedRef.current = true; return () => { + mountedRef.current = false; + deviceReadyRef.current = false; + initialInfoLoadedRef.current = false; + powerDataSubscribedRef.current = false; + readyResolverRef.current = null; // 清理校准超时定时器 if (calibrationTimeoutRef.current) { clearTimeout(calibrationTimeoutRef.current); calibrationTimeoutRef.current = null; } + if (readSuccessTimeoutRef.current) { + clearTimeout(readSuccessTimeoutRef.current); + readSuccessTimeoutRef.current = null; + } }; }, []); @@ -714,10 +920,12 @@ export default function InfoScreen({ route, navigation }: Props) { } // 蓝牙已连接,正常跳转 DFU navigation.navigate("Dfu", { - deviceId: deviceKey, - name: peripheral.name, - firmware, - }); + deviceId: deviceKey, + systemId: peripheral.systemId, + address: peripheral.address, + name: peripheral.name ?? "", + firmware: firmware ?? "", +}); }} style={styles.pressable} disabled={isLoading || powerTrimLoading} // ❌ 禁用点击 diff --git a/src/InfoScreen2.tsx b/src/InfoScreen2.tsx new file mode 100644 index 0000000..128e5fb --- /dev/null +++ b/src/InfoScreen2.tsx @@ -0,0 +1,1227 @@ +// InfoScreen2.tsx 此页面为桨频器信息页面 +import React, { useEffect, useRef, useState } from "react"; +import { + ActivityIndicator, + Pressable, + ScrollView, + StyleSheet, + Text, + View, + Alert, +} from "react-native"; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import { decode as atob } from "base-64"; +import { + Central, + ScannedPeripheral, + ConnectionStatus, +} from "@systemic-games/react-native-bluetooth-le"; +import { RootStackParamList } from "../App"; +import { useTranslation } from "react-i18next"; +import MyStatusbar from "./component/MyStatusbar"; +import MyHeader from "./component/MyHeader"; + +type Props = NativeStackScreenProps; + +const CSC_SERVICE_UUID = "1816"; +const CSC_MEASUREMENT_UUID = "2a5b"; + +const CUSTOM_SERVICE_UUID = "fff1"; +const CUSTOM_WRITE_UUID = "fff2"; +const CUSTOM_NOTIFY_UUID = "fff3"; + +const DFU_MANIFEST_URL = + "https://powerfun.oss-cn-shanghai.aliyuncs.com/yecongdfu/latest.json"; + +type BoatType = 0x00 | 0x01 | 0x02; + +type ParsedFirmware = { + hardware: number; + iteration: number; + build: string; + raw: string; +}; + +type DeviceInfo = { + hardware: number; + latestFirmware: string; + download: string; +}; + +const fullUUID = (uuid: string) => + uuid.length === 4 + ? `0000${uuid}-0000-1000-8000-00805f9b34fb` + : uuid; + +const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +const base64ToBytes = (base64: string): number[] => { + const binary = atob(base64); + const bytes: number[] = []; + + for (let i = 0; i < binary.length; i += 1) { + bytes.push(binary.charCodeAt(i) & 0xff); + } + + return bytes; +}; + +const bytesToArrayBuffer = (bytes: number[]): ArrayBuffer => { + return new Uint8Array(bytes).buffer; +}; + +const normalizeBytes = (arr: number[]): number[] => { + return arr.map((v) => v & 0xff); +}; + +const toHexByte = (value: number): string => + (value & 0xff).toString(16).toUpperCase().padStart(2, "0"); + +const bytesToHexString = (bytes: number[]): string => + normalizeBytes(bytes) + .map((v) => toHexByte(v)) + .join(" "); + +const parseFirmwareVersion = (text: string): ParsedFirmware => { + const raw = String(text ?? "").trim(); + const parts = raw.split("."); + + if (parts.length < 2) { + throw new Error(`固件版本格式不正确: ${raw}`); + } + + const hardware = parseInt(parts[0], 10); + const iteration = parseInt(parts[1], 10); + const build = parts.length >= 3 ? parts[2] : ""; + + if (Number.isNaN(hardware) || Number.isNaN(iteration)) { + throw new Error(`固件版本无法解析: ${raw}`); + } + + return { + hardware, + iteration, + build, + raw, + }; +}; + +export default function InfoScreen2({ route, navigation }: Props) { + const { t } = useTranslation(); + const { peripheral } = route.params; + const getBoatNameByValue = (value?: number | null): string => { + if (value === 0x00) return t("info2.boatKayak"); + if (value === 0x01) return t("info2.boatRowing"); + if (value === 0x02) return t("info2.boatRacing"); + return t("common.unknown"); + }; + + const deviceKey = + typeof peripheral.address === "string" && peripheral.address.length > 0 + ? peripheral.address + : String(peripheral.systemId ?? ""); + + const [cadence, setCadence] = useState(0); + const [isLoading, setIsLoading] = useState(true); + + const [selectedBoat, setSelectedBoat] = useState(0x00); + const [boatBusy, setBoatBusy] = useState(false); + const [boatStatus, setBoatStatus] = useState(t("info2.waitingAction")); + const [boatNotifyHex, setBoatNotifyHex] = useState("--"); + const [boatVerified, setBoatVerified] = useState("--"); + + const [isConnected, setIsConnected] = useState(false); + const [serial, setSerial] = useState(t("info.reading")); + const [firmware, setFirmware] = useState(t("info.reading")); + const [battery, setBattery] = useState(t("info.reading")); + + // 新增:固件升级状态 + const [latestFirmware, setLatestFirmware] = useState(t("info.reading")); + const [upgradeAvailable, setUpgradeAvailable] = useState(false); + const [upgradeCheckDone, setUpgradeCheckDone] = useState(false); + + const lastCrankRef = useRef(null); + const lastEventTimeRef = useRef(null); + const lastCadenceUpdateAppTsRef = useRef(null); + + const boatReadResolverRef = useRef<((bytes: number[]) => void) | null>(null); + const mountedRef = useRef(true); + const disconnectingRef = useRef(false); + + const hasConnectedOnceRef = useRef(false); + const hasNavigatedBackRef = useRef(false); + const skipDisconnectOnLeaveRef = useRef(false); + + const checkUpgradeNeeded = async (firmwareText: string) => { + try { + const text = String(firmwareText ?? "").trim(); + + if (!text) { + if (mountedRef.current) { + setLatestFirmware(t("common.unknown")); + setUpgradeAvailable(false); + setUpgradeCheckDone(true); + } + return; + } + + const currentFw = parseFirmwareVersion(text); + console.log("🔥 InfoScreen currentFw =", currentFw); + + const manifestResp = await fetch(DFU_MANIFEST_URL); + console.log("🔥 InfoScreen manifest status =", manifestResp.status); + + if (!manifestResp.ok) { + throw new Error(`manifest 下载失败,HTTP ${manifestResp.status}`); + } + + const manifestText = await manifestResp.text(); + console.log("🔥 InfoScreen manifest text =", manifestText); + + let manifest: { devices: DeviceInfo[] }; + + try { + manifest = JSON.parse(manifestText) as { devices: DeviceInfo[] }; + } catch (e) { + throw new Error("manifest 不是合法 JSON"); + } + + const deviceInfo = manifest.devices.find( + (d) => d.hardware === currentFw.hardware + ); + + console.log("🔥 InfoScreen matched deviceInfo =", deviceInfo); + + if (!deviceInfo) { + if (mountedRef.current) { + setLatestFirmware(t("info2.notMatched")); + setUpgradeAvailable(false); + setUpgradeCheckDone(true); + } + return; + } + + const latestFw = parseFirmwareVersion(deviceInfo.latestFirmware); + console.log("🔥 InfoScreen latestFw =", latestFw); + + if (latestFw.hardware !== currentFw.hardware) { + throw new Error( + `服务器固件硬件号不匹配:当前 ${currentFw.hardware},服务器 ${latestFw.hardware}` + ); + } + + const needUpgrade = latestFw.iteration > currentFw.iteration; + + if (mountedRef.current) { + setLatestFirmware(deviceInfo.latestFirmware); + setUpgradeAvailable(needUpgrade); + setUpgradeCheckDone(true); + } + } catch (err: any) { + console.warn("⚠️ checkUpgradeNeeded failed:", err); + if (mountedRef.current) { + setLatestFirmware(t("info2.checkFailed")); + setUpgradeAvailable(false); + setUpgradeCheckDone(true); + } + } + }; + + const readDeviceInfo = async () => { + console.log("📘 start read device info..."); + + try { + try { + const v = await Central.readCharacteristic( + peripheral, + fullUUID("180a"), + fullUUID("2a25") + ); + + if (v && (v as any).length) { + const idText = String.fromCharCode(...(v as any)); + console.log("📘 serial raw:", v, "=>", idText); + if (mountedRef.current) { + setSerial(idText); + } + } else if (mountedRef.current) { + setSerial( + (peripheral.address || + peripheral.systemId || + peripheral.name || + t("common.unknown")) as string + ); + } + } catch (err) { + console.warn("⚠️ read serial failed:", err); + if (mountedRef.current) { + setSerial( + (peripheral.address || + peripheral.systemId || + peripheral.name || + t("common.unknown")) as string + ); + } + } + + try { + const v = await Central.readCharacteristic( + peripheral, + fullUUID("180a"), + fullUUID("2a28") + ); + + if (v && (v as any).length) { + const fwText = String.fromCharCode(...(v as any)); + console.log("📘 firmware raw:", v, "=>", fwText); + if (mountedRef.current) { + setFirmware(fwText); + } + + // 新增:在 Info 页提前检查是否需要升级 + await checkUpgradeNeeded(fwText); + } else if (mountedRef.current) { + setFirmware(t("common.unknown")); + setLatestFirmware(t("common.unknown")); + setUpgradeAvailable(false); + setUpgradeCheckDone(true); + } + } catch (err) { + console.warn("⚠️ read firmware failed:", err); + if (mountedRef.current) { + setFirmware(t("common.unknown")); + setLatestFirmware(t("common.unknown")); + setUpgradeAvailable(false); + setUpgradeCheckDone(true); + } + } + + try { + const v = await Central.readCharacteristic( + peripheral, + fullUUID("180f"), + fullUUID("2a19") + ); + + if (v && (v as any).length) { + const batteryValue = (v as any)[0]; + console.log("📘 battery raw:", v, "=>", batteryValue); + if (mountedRef.current) { + setBattery(`${batteryValue}%`); + } + } else if (mountedRef.current) { + setBattery(t("common.unknown")); + } + } catch (err) { + console.warn("⚠️ read battery failed:", err); + if (mountedRef.current) { + setBattery(t("common.unknown")); + } + } + } catch (err) { + console.warn("⚠️ readDeviceInfo failed:", err); + if (mountedRef.current) { + setSerial( + (peripheral.address || + peripheral.systemId || + peripheral.name || + t("common.unknown")) as string + ); + setFirmware(t("common.unknown")); + setBattery(t("common.unknown")); + setLatestFirmware(t("common.unknown")); + setUpgradeAvailable(false); + setUpgradeCheckDone(true); + } + } + }; + + const writeBytesToFff2 = async (bytes: number[]) => { + const normalized = normalizeBytes(bytes); + const buffer = bytesToArrayBuffer(normalized); + + console.log("✍️ write FFF2 hex:", bytesToHexString(normalized)); + + await Central.writeCharacteristic( + peripheral, + fullUUID(CUSTOM_SERVICE_UUID), + fullUUID(CUSTOM_WRITE_UUID), + buffer, + { withoutResponse: false } + ); + }; + + const waitBoatNotifyOnce = (timeoutMs = 3000): Promise => { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + if (boatReadResolverRef.current) { + boatReadResolverRef.current = null; + } + reject(new Error(t("info2.waitFff3Timeout"))); + }, timeoutMs); + + boatReadResolverRef.current = (bytes: number[]) => { + const normalized = normalizeBytes(bytes); + + if (normalized.length < 1) { + console.log("⚠️ 忽略 FFF3:长度不足 1 字节"); + return; + } + + clearTimeout(timer); + boatReadResolverRef.current = null; + resolve(normalized); + }; + }); + }; + + const applyBoatTypeFromNotify = (notifyBytes: number[]) => { + let returnedBoatType: number | null = null; + + if (notifyBytes.length === 1) { + returnedBoatType = notifyBytes[0]; + } else if (notifyBytes[0] === 0x01 && notifyBytes.length >= 2) { + returnedBoatType = notifyBytes[1]; + } else { + returnedBoatType = notifyBytes[0]; + } + + if ( + returnedBoatType === 0x00 || + returnedBoatType === 0x01 || + returnedBoatType === 0x02 + ) { + setSelectedBoat(returnedBoatType as BoatType); + setBoatVerified( + t("info2.currentBoat", { boatName: getBoatNameByValue(returnedBoatType) }) + ); + setBoatStatus( + t("info2.readCurrentBoat", { + boatName: getBoatNameByValue(returnedBoatType), + }) + ); + console.log( + "🚣 apply boat type:", + returnedBoatType, + getBoatNameByValue(returnedBoatType) + ); + } else { + setBoatVerified(`❌ ${t("info2.unknownBoatType")}`); + setBoatStatus(t("info2.readAbnormal", { hex: bytesToHexString(notifyBytes) })); + console.log("⚠️ unknown boat notify:", bytesToHexString(notifyBytes)); + } + }; + + const handleSetBoatType = async (boatType: BoatType) => { + if (boatBusy) return; + + try { + setBoatBusy(true); + setBoatVerified("--"); + setBoatNotifyHex("--"); + + const boatName = getBoatNameByValue(boatType); + + setBoatStatus(t("info2.writingBoat", { boatName })); + await writeBytesToFff2([0x01, boatType]); + + setBoatStatus(t("info2.writtenWaitRead", { boatName })); + await sleep(200); + + setBoatStatus(t("info2.requestingBoatRead")); + const waitPromise = waitBoatNotifyOnce(3000); + + await writeBytesToFff2([0x02]); + + const notifyBytes = await waitPromise; + const hex = bytesToHexString(notifyBytes); + setBoatNotifyHex(hex); + + let returnedBoatType: number | null = null; + + if (notifyBytes.length === 1) { + returnedBoatType = notifyBytes[0]; + } else if (notifyBytes[0] === 0x01) { + returnedBoatType = notifyBytes[1]; + } else { + returnedBoatType = notifyBytes[0]; + } + + const ok = returnedBoatType === boatType; + + if (ok) { + applyBoatTypeFromNotify(notifyBytes); + setBoatStatus(t("info2.setSuccess", { boatName })); + } else { + setBoatVerified( + `❌ ${t("info2.notMatch", { + expected: boatName, + actual: getBoatNameByValue(returnedBoatType), + })}` + ); + setBoatStatus(t("info2.readResult", { hex })); + } + } catch (error: any) { + console.warn("❌ set boat type failed:", error); + setBoatStatus( + t("info2.actionFailed", { message: error?.message ?? "unknown error" }) + ); + setBoatVerified(`❌ ${t("info2.failed")}`); + } finally { + setBoatBusy(false); + } + }; + + const goToDfu = () => { + if (!isConnected) { + Alert.alert(t("common.notice"), t("info2.disconnectedNeedReconnect"), [ + { + text: t("info.confirm"), + onPress: () => { + navigation.goBack(); + }, + }, + ]); + return; + } + + skipDisconnectOnLeaveRef.current = true; + console.log("🔥 peripheral =", JSON.stringify(peripheral, null, 2)); + console.log("🔥 peripheral.address =", peripheral.address); + console.log("🔥 peripheral.systemId =", peripheral.systemId); + console.log("🔥 peripheral.id =", peripheral.id); + console.log("🔥 peripheral.identifier =", peripheral.identifier); + console.log("🔥 peripheral.uuid =", peripheral.uuid); + console.log("🔥 current deviceKey =", deviceKey); + + navigation.navigate("Dfu", { + deviceId: deviceKey, + systemId: peripheral.systemId, + address: peripheral.address, + name: peripheral.name ?? "", + firmware: firmware ?? "", + }); + }; + + useEffect(() => { + const connectionHandler = async (ev: { + peripheral: ScannedPeripheral; + connectionStatus: ConnectionStatus; + }) => { + const addr = ev.peripheral.address || ev.peripheral.systemId; + if (addr !== deviceKey) return; + + console.log("🔌 connection event:", ev.connectionStatus); + + const connected = + ev.connectionStatus === "connected" || ev.connectionStatus === "ready"; + + setIsConnected(connected); + + if (connected) { + hasConnectedOnceRef.current = true; + return; + } + + if ( + hasConnectedOnceRef.current && + mountedRef.current && + !disconnectingRef.current && + !hasNavigatedBackRef.current + ) { + hasNavigatedBackRef.current = true; + + console.log("↩️ 检测到设备断开,自动返回上一级"); + + if (navigation.canGoBack()) { + navigation.goBack(); + } + } + }; + + Central.addListener("peripheralConnectionStatus", connectionHandler); + return () => { + Central.removeListener("peripheralConnectionStatus", connectionHandler); + }; + }, [deviceKey, navigation]); + + useEffect(() => { + const unsubscribe = navigation.addListener("beforeRemove", async (e) => { + if (disconnectingRef.current) return; + disconnectingRef.current = true; + + const next = e.data.action?.type; + const action = e.data.action as any; + + if (next === "NAVIGATE" && action?.payload?.name === "Dfu") { + console.log("➡️ 跳转到 DFU,不断开蓝牙"); + disconnectingRef.current = false; + return; + } + + console.log("📴 页面关闭,断开蓝牙..."); + try { + await Central.disconnectPeripheral(peripheral); + console.log("✅ 已断开蓝牙"); + } catch (err) { + console.warn("❌ 断开失败:", err); + } finally { + disconnectingRef.current = false; + } + }); + + return unsubscribe; + }, [navigation, peripheral]); + + useEffect(() => { + mountedRef.current = true; + + let cscSubscription: any = null; + let customSubscription: any = null; + let cadenceZeroTimer: ReturnType | null = null; + + const handleMeasurement = (ev: any) => { + const rawData: number[] = Array.isArray(ev?.value) + ? ev.value + : typeof ev?.value === "string" + ? base64ToBytes(ev.value) + : ev?.value instanceof ArrayBuffer + ? Array.from(new Uint8Array(ev.value)) + : []; + + const data = normalizeBytes(rawData); + + if (data.length === 0) { + console.log("📥 CSC raw data: "); + return; + } + + console.log("📥 CSC raw data hex:", bytesToHexString(data)); + + if (data.length < 5) { + console.log("⚠️ invalid CSC packet length:", data.length); + return; + } + + const flags = data[0]; + + if (flags !== 0x02) { + console.log("⚠️ unexpected CSC flags:", toHexByte(flags)); + return; + } + + const crankRevolutions = data[1] | (data[2] << 8); + const crankEventTime = data[3] | (data[4] << 8); + + const lastCrank = lastCrankRef.current; + const lastEventTime = lastEventTimeRef.current; + + console.log( + "📊 CSC parsed:", + "crankRevolutions=", + crankRevolutions, + "crankEventTime=", + crankEventTime, + "lastCrank=", + lastCrank, + "lastEventTime=", + lastEventTime + ); + + if (lastCrank !== null && lastEventTime !== null) { + let deltaCrank = crankRevolutions - lastCrank; + let deltaTime = crankEventTime - lastEventTime; + + if (deltaCrank < 0) deltaCrank += 0x10000; + if (deltaTime < 0) deltaTime += 0x10000; + + console.log( + "📊 CSC delta:", + "deltaCrank=", + deltaCrank, + "deltaTime=", + deltaTime + ); + + if (deltaCrank > 0 && deltaTime > 0) { + if (deltaCrank <= 4) { + const rpm = (60 * 1024 * deltaCrank) / deltaTime; + + console.log("📊 CSC rpm raw:", rpm); + + if (rpm >= 1 && rpm <= 220) { + const roundedCadence = Math.round(rpm); + const appNow = Date.now(); + + if (mountedRef.current) { + setCadence(roundedCadence); + } + + lastCadenceUpdateAppTsRef.current = appNow; + + console.log( + "✅ cadence updated:", + "cadence=", + roundedCadence, + "appTs=", + appNow, + "bleCrank=", + crankRevolutions, + "bleEventTime=", + crankEventTime + ); + } else { + console.log("⚠️ rpm out of range, ignored:", rpm); + } + } else { + console.log("⚠️ deltaCrank too large, ignored:", deltaCrank); + } + } else { + console.log( + "⚠️ delta invalid, ignored:", + "deltaCrank=", + deltaCrank, + "deltaTime=", + deltaTime + ); + } + } else { + console.log( + "🟡 first valid CSC packet, baseline saved only:", + "crankRevolutions=", + crankRevolutions, + "crankEventTime=", + crankEventTime + ); + } + + lastCrankRef.current = crankRevolutions; + lastEventTimeRef.current = crankEventTime; + }; + + const handleBoatNotify = (ev: any) => { + const rawData: number[] = Array.isArray(ev?.value) + ? ev.value + : typeof ev?.value === "string" + ? base64ToBytes(ev.value) + : ev?.value instanceof ArrayBuffer + ? Array.from(new Uint8Array(ev.value)) + : []; + + const data = normalizeBytes(rawData); + + if (data.length === 0) { + console.log("📥 FFF3 raw data: "); + return; + } + + const hex = bytesToHexString(data); + console.log("📥 FFF3 raw data hex:", hex); + + if (mountedRef.current) { + setBoatNotifyHex(hex); + } + + if (boatReadResolverRef.current) { + boatReadResolverRef.current(data); + } + }; + + (async () => { + setIsLoading(true); + + try { + await Central.connectPeripheral(peripheral); + await new Promise((resolve) => setTimeout(resolve, 500)); + setIsConnected(true); + hasConnectedOnceRef.current = true; + hasNavigatedBackRef.current = false; + + console.log("📡 subscribe CSC:", CSC_MEASUREMENT_UUID); + cscSubscription = await Central.subscribeCharacteristic( + peripheral, + fullUUID(CSC_SERVICE_UUID), + fullUUID(CSC_MEASUREMENT_UUID), + handleMeasurement + ); + + console.log("📡 subscribe FFF3:", CUSTOM_NOTIFY_UUID); + customSubscription = await Central.subscribeCharacteristic( + peripheral, + fullUUID(CUSTOM_SERVICE_UUID), + fullUUID(CUSTOM_NOTIFY_UUID), + handleBoatNotify + ); + + await readDeviceInfo(); + + cadenceZeroTimer = setInterval(() => { + const lastUpdateTs = lastCadenceUpdateAppTsRef.current; + const now = Date.now(); + + if (lastUpdateTs === null) { + return; + } + + const elapsed = now - lastUpdateTs; + + if (elapsed > 3000) { + if (mountedRef.current) { + setCadence((prev) => { + if (prev !== 0) { + console.log( + "⏱️ cadence reset to 0 and baseline cleared:", + "now=", + now, + "lastCadenceUpdateAppTs=", + lastUpdateTs, + "elapsedMs=", + elapsed + ); + return 0; + } + return prev; + }); + } + + lastCrankRef.current = null; + lastEventTimeRef.current = null; + lastCadenceUpdateAppTsRef.current = null; + } + }, 500); + + console.log("✅ all subscribe success"); + + try { + console.log("🔎 default read boat type: write FFF2 = 02"); + const waitPromise = waitBoatNotifyOnce(3000); + await writeBytesToFff2([0x02]); + const notifyBytes = await waitPromise; + + if (mountedRef.current) { + setBoatNotifyHex(bytesToHexString(notifyBytes)); + applyBoatTypeFromNotify(notifyBytes); + } + } catch (err) { + console.warn("⚠️ default read boat type failed:", err); + if (mountedRef.current) { + setBoatStatus(t("info2.defaultReadFailed")); + } + } + + if (mountedRef.current) { + setIsLoading(false); + } + } catch (e) { + console.warn("❌ 读取失败", e); + if (mountedRef.current) { + setIsLoading(false); + setIsConnected(false); + setBoatStatus(t("info2.connectOrReadFailed")); + Alert.alert(t("common.notice"), t("info2.connectOrReadFailedAlert")); + } + } + })(); + + return () => { + mountedRef.current = false; + hasConnectedOnceRef.current = false; + hasNavigatedBackRef.current = false; + setIsConnected(false); + + try { + if (cscSubscription?.remove) { + cscSubscription.remove(); + } + } catch (err) { + console.warn("⚠️ csc subscription remove failed:", err); + } + + try { + if (customSubscription?.remove) { + customSubscription.remove(); + } + } catch (err) { + console.warn("⚠️ custom subscription remove failed:", err); + } + + if (cadenceZeroTimer) { + clearInterval(cadenceZeroTimer); + cadenceZeroTimer = null; + } + + boatReadResolverRef.current = null; + lastCrankRef.current = null; + lastEventTimeRef.current = null; + lastCadenceUpdateAppTsRef.current = null; + + try { + if (!skipDisconnectOnLeaveRef.current) { + Central.disconnectPeripheral(peripheral); + } else { + console.log("➡️ cleanup: 跳转到 DFU,不断开蓝牙"); + } + } catch {} + }; + }, [peripheral]); + + return ( + <> + + + + {isLoading ? ( + + ) : ( + <> + {/* 1. 桨频数据框 */} + + {t("info2.cadence")} + {cadence} + {t("info2.cadenceUnit")} + + + {/* 2. 船型选择 */} + + + {t("info2.boatSelect")} + + {boatBusy ? ( + {t("info2.boatSwitching")} + ) : boatVerified.startsWith("✅") ? ( + + ) : boatVerified.startsWith("❌") ? ( + ❌{t("info2.retry")} + ) : ( + - + )} + + + + + handleSetBoatType(0x00)} + disabled={boatBusy} + > + + {t("info2.boatKayak")} + + + + handleSetBoatType(0x01)} + disabled={boatBusy} + > + + {t("info2.boatRowing")} + + + + handleSetBoatType(0x02)} + disabled={boatBusy} + > + + {t("info2.boatRacing")} + + + + + + {/* 3. 设备信息(内含固件升级按钮) */} + + + {t("info.title")} + + + + {t("info.bluetoothName")} + {peripheral?.name || t("common.unknown")} + + + + {t("info.idNumber")} + {serial} + + + + {t("info.firmwareVersion")} + + {firmware} + + + {t("info.firmwareUpgrade")} + + + + + + + {t("info.battery")} + {battery} + + + + {t("info2.latestFirmware")} + + {upgradeCheckDone ? latestFirmware : t("info2.checking")} + + + + + {t("info.connectionStatus")} + + {isConnected ? t("info.connected") : t("info.disconnected")} + + + + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#f2f3f7", + }, + contentContainer: { + padding: 16, + gap: 12, + backgroundColor: "#f2f3f7", + }, + + cadenceCard: { + backgroundColor: "#FFFFFF", + borderRadius: 12, + borderWidth: 1, + borderColor: "#E7141E", + paddingVertical: 58, + paddingHorizontal: 20, + alignItems: "center", + marginBottom: 4, + }, + cadenceLabel: { + color: "#666666", + fontSize: 20, + marginBottom: 12, + fontWeight: "500", + }, + cadenceValue: { + color: "#E7141E", + fontSize: 100, + fontWeight: "700", + lineHeight: 108, + }, + cadenceUnit: { + color: "#666666", + fontSize: 18, + marginTop: 10, + }, + + sectionCard: { + backgroundColor: "#FFFFFF", + borderRadius: 12, + borderWidth: 1, + borderColor: "#E7141E", + padding: 16, + marginBottom: 4, + }, + sectionHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: 16, + }, + sectionTitle: { + color: "#111111", + fontSize: 16, + fontWeight: "600", + }, + statusContainer: { + flexDirection: "row", + alignItems: "center", + }, + statusTextBusy: { + color: "#FF9800", + fontSize: 14, + fontWeight: "500", + }, + statusTextSuccess: { + color: "#4CAF50", + fontSize: 20, + fontWeight: "bold", + }, + statusTextError: { + color: "#F44336", + fontSize: 16, + fontWeight: "bold", + }, + statusText: { + color: "#444444", + fontSize: 14, + }, + buttonRow: { + flexDirection: "row", + justifyContent: "space-between", + gap: 10, + }, + boatButton: { + flex: 1, + backgroundColor: "#FFFFFF", + borderWidth: 1, + borderColor: "#E7141E", + borderRadius: 10, + paddingVertical: 10, + alignItems: "center", + }, + boatButtonActive: { + backgroundColor: "#E7141E", + }, + boatButtonText: { + color: "#E7141E", + fontSize: 15, + fontWeight: "600", + }, + boatButtonTextActive: { + color: "#FFFFFF", + }, + + infoCard: { + backgroundColor: "#FFFFFF", + borderRadius: 12, + borderWidth: 1, + borderColor: "#E7141E", + paddingHorizontal: 14, + paddingVertical: 4, + }, + infoHeader: { + paddingBottom: 4, + }, + infoTitle: { + color: "#111111", + fontSize: 16, + fontWeight: "600", + }, + infoRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingVertical: 5, + borderBottomWidth: 1, + borderBottomColor: "#F0F0F0", + }, + infoRowLast: { + borderBottomWidth: 0, + }, + infoLabel: { + color: "#666666", + fontSize: 13, + flexShrink: 0, + marginRight: 12, + }, + infoValue: { + color: "#111111", + fontSize: 14, + fontWeight: "600", + }, + firmwareRightWrap: { + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + flexShrink: 1, + }, + inlineDfuButton: { + marginLeft: 10, + backgroundColor: "#D9D9D9", + borderRadius: 8, + paddingHorizontal: 10, + paddingVertical: 5, + }, + inlineDfuButtonHot: { + backgroundColor: "#E7141E", + }, + inlineDfuButtonDisabled: { + opacity: 0.6, + }, + inlineDfuButtonText: { + color: "#333333", + fontSize: 12, + fontWeight: "600", + }, + inlineDfuButtonTextHot: { + color: "#FFFFFF", + }, + connectedText: { + color: "#0A9F4B", + }, + disconnectedText: { + color: "#D43C3C", + }, +}); diff --git a/src/InfoScreen3.tsx b/src/InfoScreen3.tsx new file mode 100644 index 0000000..34ee0b5 --- /dev/null +++ b/src/InfoScreen3.tsx @@ -0,0 +1,2049 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { + View, + Text as RNText, + StyleSheet, + ScrollView, + ActivityIndicator, + TouchableOpacity, + TextInput, + Alert, + Pressable, + Modal, + Keyboard, +} from "react-native"; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import { + Central, + ScannedPeripheral, + ConnectionStatus, +} from "@systemic-games/react-native-bluetooth-le"; +import { RootStackParamList } from "../App"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; +import MyStatusbar from "./component/MyStatusbar"; +import MyHeader from "./component/MyHeader"; +import { useTranslation } from "react-i18next"; +import { observeFtmsIndoorBikeData } from "./helper/ftmsIndoorBikeDataBus"; + +type Props = NativeStackScreenProps; + +type TooltipKey = "weight" | "trim" | "bikeType" | "ergSmooth" | null; +type DropdownKey = "bikeType" | "ergSmooth" | null; +type PendingAction = "weight" | "powerTrim" | null; +type Fff3WaitType = + | "powerTrimRead" + | "powerTrimAck" + | "weightRead" + | "weightAck" + | "bikeTypeRead" + | "bikeTypeAck" + | "ergSmoothRead" + | "ergSmoothAck" + | "usedMileageRead" + | null; + +const powerServiceUuid = "fff1"; +const powerWriteUuid = "fff2"; +const powerNotifyUuid = "fff3"; + +const fullUUID = (uuid: string) => + uuid.length === 4 + ? `0000${uuid}-0000-1000-8000-00805f9b34fb` + : uuid; + +const Text = ({ children, style, ...props }: any) => { + return ( + + {children} + + ); +}; + +export default function InfoScreen3({ route, navigation }: Props) { + const { peripheral } = route.params; + const { t } = useTranslation(); + + const deviceKey = useMemo( + () => peripheral.address || peripheral.systemId, + [peripheral.address, peripheral.systemId] + ); + + const [power, setPower] = useState(0); + const [cadence, setCadence] = useState(0); + const [speedKph, setSpeedKph] = useState(0); + + const [isConnected, setIsConnected] = useState(false); + const [isFtmsSessionReady, setIsFtmsSessionReady] = useState(false); + const [isFff3Ready, setIsFff3Ready] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + const [serial, setSerial] = useState(t("info.reading")); + const [firmware, setFirmware] = useState(t("info.reading")); + const [, setHardware] = useState(t("info.reading")); + const [, setBattery] = useState(t("info.reading")); + + // 当前已生效值 + const [weight, setWeight] = useState("70"); + const [powerTrim, setPowerTrim] = useState("100.00"); + + // 编辑中的草稿值 + const [pendingWeight, setPendingWeight] = useState("70"); + const [pendingPowerTrim, setPendingPowerTrim] = useState("100.00"); + + const [bikeType, setBikeType] = useState(t("info3.bikeTypeFollow")); + const [ergSmooth, setErgSmooth] = useState(t("info3.ergOff")); + const [usedMileage, setUsedMileage] = useState(t("info.reading")); + + const [confirmModalVisible, setConfirmModalVisible] = useState(false); + const [progressModalVisible, setProgressModalVisible] = useState(false); + const [progressText, setProgressText] = useState(""); + const [pendingAction, setPendingAction] = useState(null); + + const [visibleTooltip, setVisibleTooltip] = useState(null); + const [openDropdown, setOpenDropdown] = useState(null); + + const [dropdownOptions, setDropdownOptions] = useState([]); + const [dropdownTitle, setDropdownTitle] = useState(""); + const [dropdownOnSelect, setDropdownOnSelect] = useState< + ((value: string) => void | Promise) | null + >(null); + + const [isSettingBikeType, setIsSettingBikeType] = useState(false); + const [isSettingErgSmooth, setIsSettingErgSmooth] = useState(false); + const [isInitializingSettings, setIsInitializingSettings] = useState(false); + + const notifySubscribedRef = useRef(false); + const disconnectingRef = useRef(false); + const mountedRef = useRef(true); + const hasConnectedOnceRef = useRef(false); + const hasNavigatedBackRef = useRef(false); + const skipDisconnectOnLeaveRef = useRef(false); + + const fff3WaitTypeRef = useRef(null); + const fff3ResolverRef = useRef<((value: Uint8Array) => void) | null>(null); + const fff3RejecterRef = useRef<((reason?: any) => void) | null>(null); + const fff3TimeoutRef = useRef | null>(null); + const fff3OperationQueueRef = useRef>(Promise.resolve()); + + const clearFff3Waiter = () => { + fff3WaitTypeRef.current = null; + fff3ResolverRef.current = null; + fff3RejecterRef.current = null; + if (fff3TimeoutRef.current) { + clearTimeout(fff3TimeoutRef.current); + fff3TimeoutRef.current = null; + } + }; + + const decodeBase64 = (value: string) => { + const atobFn = (globalThis as { atob?: (data: string) => string }).atob; + + if (typeof atobFn === "function") { + const binary = atobFn(value); + const bytes = new Uint8Array(binary.length); + + for (let i = 0; i < binary.length; i += 1) { + bytes[i] = binary.charCodeAt(i); + } + + return bytes; + } + + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let buffer = 0; + let bits = 0; + const output: number[] = []; + + for (const char of value.replace(/=+$/, "")) { + const index = chars.indexOf(char); + if (index < 0) continue; + + buffer = (buffer << 6) | index; + bits += 6; + + if (bits >= 8) { + bits -= 8; + output.push((buffer >> bits) & 0xff); + } + } + + return new Uint8Array(output); + }; + + const toUint8Array = (raw: unknown): Uint8Array | null => { + try { + if (!raw) return null; + if (raw instanceof Uint8Array) return raw; + if (raw instanceof ArrayBuffer) return new Uint8Array(raw); + if (typeof raw === "string") return decodeBase64(raw); + if (Array.isArray(raw)) return new Uint8Array(raw); + + if ( + typeof raw === "object" && + raw !== null && + "buffer" in raw && + (raw as { buffer?: unknown }).buffer instanceof ArrayBuffer + ) { + const view = raw as { + buffer: ArrayBuffer; + byteOffset?: number; + byteLength?: number; + }; + return new Uint8Array( + view.buffer, + view.byteOffset || 0, + view.byteLength + ); + } + + return null; + } catch { + return null; + } + }; + + const enqueueFff3Operation = async (task: () => Promise) => { + const previous = fff3OperationQueueRef.current; + let release!: () => void; + + fff3OperationQueueRef.current = new Promise((resolve) => { + release = resolve; + }); + + await previous; + + try { + return await task(); + } finally { + release(); + } + }; + + const floatWeightToDeviceValue = (weightKgText: string) => { + const weightNum = Number(weightKgText); + return Math.round(weightNum * 100); + }; + + const deviceValueToWeightText = (deviceValue: number) => { + return (deviceValue / 100).toFixed(2).replace(/\.?0+$/, ""); + }; + + const isValidWeightInput = (text: string) => { + if (!text) return false; + if (!/^\d+(\.\d{1,2})?$/.test(text)) return false; + const num = Number(text); + return Number.isFinite(num) && num > 0 && num <= 300; + }; + + const powerTrimTextToDeviceValue = (trimText: string) => { + const trimNum = Number(trimText); + return Math.round(trimNum * 100); + }; + + const deviceValueToPowerTrimText = (deviceValue: number) => { + return (deviceValue / 100).toFixed(2); + }; + + const isValidPowerTrimInput = (text: string) => { + if (!text) return false; + if (!/^\d+(\.\d{1,2})?$/.test(text)) return false; + const num = Number(text); + return Number.isFinite(num) && num >= 50 && num <= 200; + }; + + const bikeTypeToCode = (text: string) => { + switch (text) { + case t("info3.bikeTypeFollow"): + return 0x00; + case t("info3.bikeTypeRoad"): + return 0x01; + case t("info3.bikeTypeMtb26"): + return 0x02; + case t("info3.bikeTypeMtb275"): + return 0x03; + case t("info3.bikeTypeMtb29"): + return 0x04; + case t("info3.bikeTypeSmallWheel"): + return 0x05; + default: + return 0x00; + } + }; + + const codeToBikeType = (code: number) => { + switch (code) { + case 0x00: + return t("info3.bikeTypeFollow"); + case 0x01: + return t("info3.bikeTypeRoad"); + case 0x02: + return t("info3.bikeTypeMtb26"); + case 0x03: + return t("info3.bikeTypeMtb275"); + case 0x04: + return t("info3.bikeTypeMtb29"); + case 0x05: + return t("info3.bikeTypeSmallWheel"); + default: + return t("info3.bikeTypeFollow"); + } + }; + + const ergSmoothToCode = (text: string) => { + switch (text) { + case t("info3.ergOff"): + return 0x00; + case t("info3.ergOn"): + return 0x01; + default: + return 0x00; + } + }; + + const codeToErgSmooth = (code: number) => { + switch (code) { + case 0x00: + return t("info3.ergOff"); + case 0x01: + return t("info3.ergOn"); + default: + return t("info3.ergOff"); + } + }; + + const parseUsedHoursFromFff3 = (value: Uint8Array): number | null => { + if (value.length < 3) return null; + if (value[0] !== 0x0b) return null; + const raw = value[1] | (value[2] << 8); + return raw / 10.0; + }; + + const hasUnsavedWeight = pendingWeight.trim() !== weight; + const hasUnsavedPowerTrim = pendingPowerTrim.trim() !== powerTrim; + + const subscribeFff3IfNeeded = async () => { + if (notifySubscribedRef.current) return; + + try { + await Central.unsubscribeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerNotifyUuid) + ).catch(() => {}); + + await Central.subscribeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerNotifyUuid), + (notifyEv) => { + try { + const bytes = toUint8Array(notifyEv.value); + if (!bytes || bytes.length === 0) return; + + if (fff3ResolverRef.current) { + fff3ResolverRef.current(bytes); + } + } catch (err) { + console.warn("处理 FFF3 通知失败:", err); + if (fff3RejecterRef.current) { + fff3RejecterRef.current(err); + } + clearFff3Waiter(); + } + } + ); + + notifySubscribedRef.current = true; + setIsFff3Ready(true); + } catch (err) { + console.warn("FFF3 notify 订阅失败:", err); + setIsFff3Ready(false); + } + }; + + const waitPowerTrimAck = (expectedValue: number, timeout = 3000) => { + return new Promise((resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "powerTrimAck"; + + fff3ResolverRef.current = (value: Uint8Array) => { + const low = expectedValue & 0xff; + const high = (expectedValue >> 8) & 0xff; + + if ( + value.length >= 3 && + value[0] === 0x01 && + value[1] === low && + value[2] === high + ) { + clearFff3Waiter(); + resolve(); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.waitFff3Timeout"))); + }, timeout); + }); + }; + + const requestCurrentPowerTrim = (timeout = 3000) => { + return enqueueFff3Operation( + () => + new Promise(async (resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "powerTrimRead"; + + fff3ResolverRef.current = (value: Uint8Array) => { + if (value.length >= 3 && value[0] === 0x01) { + const currentTrim = value[1] | (value[2] << 8); + clearFff3Waiter(); + resolve(currentTrim); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.readPowerTrimTimeout"))); + }, timeout); + + try { + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x02]).buffer, + { withoutResponse: false } + ); + } catch (err) { + clearFff3Waiter(); + reject(err); + } + }) + ); + }; + + const waitWeightAck = (expectedWeightValue: number, timeout = 3000) => { + return new Promise((resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "weightAck"; + + fff3ResolverRef.current = (value: Uint8Array) => { + const low = expectedWeightValue & 0xff; + const high = (expectedWeightValue >> 8) & 0xff; + + if ( + (value.length >= 4 && + value[0] === 0x0e && + value[1] === low && + value[2] === high && + value[3] === 0x00) || + (value.length >= 4 && + value[0] === 0x0f && + value[1] === low && + value[2] === high && + value[3] === 0x00) || + (value.length >= 3 && + value[0] === 0x0e && + value[1] === low && + value[2] === high) + ) { + clearFff3Waiter(); + resolve(); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.waitWeightAckTimeout"))); + }, timeout); + }); + }; + + const requestCurrentWeight = (timeout = 3000) => { + return enqueueFff3Operation( + () => + new Promise(async (resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "weightRead"; + + fff3ResolverRef.current = (value: Uint8Array) => { + if (value.length >= 4 && value[0] === 0x0e && value[3] === 0x00) { + const weightDeviceValue = value[1] | (value[2] << 8); + clearFff3Waiter(); + resolve(weightDeviceValue); + return; + } + + if (value.length >= 4 && value[0] === 0x0f && value[3] === 0x00) { + const weightDeviceValue = value[1] | (value[2] << 8); + clearFff3Waiter(); + resolve(weightDeviceValue); + return; + } + + if (value.length >= 3 && value[0] === 0x0e) { + const weightDeviceValue = value[1] | (value[2] << 8); + clearFff3Waiter(); + resolve(weightDeviceValue); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.readWeightTimeout"))); + }, timeout); + + try { + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x0f]).buffer, + { withoutResponse: false } + ); + } catch (err) { + clearFff3Waiter(); + reject(err); + } + }) + ); + }; + + const requestCurrentBikeType = (timeout = 3000) => { + return enqueueFff3Operation( + () => + new Promise(async (resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "bikeTypeRead"; + + fff3ResolverRef.current = (value: Uint8Array) => { + if ( + value.length >= 3 && + ((value[0] === 0x05 && value[2] === 0x00) || + (value[0] === 0x06 && value[2] === 0x00)) + ) { + clearFff3Waiter(); + resolve(value[1]); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.readBikeTypeTimeout"))); + }, timeout); + + try { + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x06]).buffer, + { withoutResponse: false } + ); + } catch (err) { + clearFff3Waiter(); + reject(err); + } + }) + ); + }; + + const waitBikeTypeAck = (expectedCode: number, timeout = 3000) => { + return new Promise((resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "bikeTypeAck"; + + fff3ResolverRef.current = (value: Uint8Array) => { + if ( + value.length >= 3 && + value[1] === expectedCode && + value[2] === 0x00 && + (value[0] === 0x05 || value[0] === 0x06) + ) { + clearFff3Waiter(); + resolve(); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.waitBikeTypeAckTimeout"))); + }, timeout); + }); + }; + + const requestCurrentErgSmooth = (timeout = 3000) => { + return enqueueFff3Operation( + () => + new Promise(async (resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "ergSmoothRead"; + + fff3ResolverRef.current = (value: Uint8Array) => { + if ( + value.length >= 3 && + ((value[0] === 0x03 && value[2] === 0x00) || + (value[0] === 0x04 && value[2] === 0x00)) + ) { + clearFff3Waiter(); + resolve(value[1]); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.readErgTimeout"))); + }, timeout); + + try { + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x04]).buffer, + { withoutResponse: false } + ); + } catch (err) { + clearFff3Waiter(); + reject(err); + } + }) + ); + }; + + const waitErgSmoothAck = (expectedCode: number, timeout = 3000) => { + return new Promise((resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "ergSmoothAck"; + + fff3ResolverRef.current = (value: Uint8Array) => { + if ( + value.length >= 3 && + value[1] === expectedCode && + value[2] === 0x00 && + (value[0] === 0x03 || value[0] === 0x04) + ) { + clearFff3Waiter(); + resolve(); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.waitErgAckTimeout"))); + }, timeout); + }); + }; + + const requestUsedHours = (timeout = 3000) => { + return enqueueFff3Operation( + () => + new Promise(async (resolve, reject) => { + clearFff3Waiter(); + fff3WaitTypeRef.current = "usedMileageRead"; + + fff3ResolverRef.current = (value: Uint8Array) => { + const parsedHours = parseUsedHoursFromFff3(value); + if (parsedHours !== null) { + clearFff3Waiter(); + resolve(parsedHours); + } + }; + + fff3RejecterRef.current = reject; + fff3TimeoutRef.current = setTimeout(() => { + clearFff3Waiter(); + reject(new Error(t("info3.readUsedHoursTimeout"))); + }, timeout); + + try { + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x0c]).buffer, + { withoutResponse: false } + ); + } catch (err) { + clearFff3Waiter(); + reject(err); + } + }) + ); + }; + + const applyPowerTrim = async (trimText: string) => { + return enqueueFff3Operation(async () => { + if (!isValidPowerTrimInput(trimText)) { + throw new Error(t("info3.powerTrimRangeError")); + } + + const trimValue = powerTrimTextToDeviceValue(trimText); + const low = trimValue & 0xff; + const high = (trimValue >> 8) & 0xff; + + if (!isConnected) { + throw new Error(t("info.deviceNotConnected")); + } + + if (!isFff3Ready) { + throw new Error(t("info3.deviceNotReady")); + } + + const ackPromise = waitPowerTrimAck(trimValue, 3000); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x01, low, high]).buffer, + { withoutResponse: false } + ); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x02]).buffer, + { withoutResponse: false } + ); + + await ackPromise; + }); + }; + + const applyWeight = async (weightText: string) => { + return enqueueFff3Operation(async () => { + if (!isValidWeightInput(weightText)) { + throw new Error(t("info3.invalidWeight")); + } + + if (!isConnected) { + throw new Error(t("info.deviceNotConnected")); + } + + if (!isFff3Ready) { + throw new Error(t("info3.deviceNotReady")); + } + + const deviceWeight = floatWeightToDeviceValue(weightText); + const low = deviceWeight & 0xff; + const high = (deviceWeight >> 8) & 0xff; + + const ackPromise = waitWeightAck(deviceWeight, 3000); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x0e, low, high]).buffer, + { withoutResponse: false } + ); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x0f]).buffer, + { withoutResponse: false } + ); + + await ackPromise; + }); + }; + + const applyBikeType = async (bikeTypeText: string) => { + return enqueueFff3Operation(async () => { + if (!isConnected) { + throw new Error(t("info.deviceNotConnected")); + } + + if (!isFff3Ready) { + throw new Error(t("info3.deviceNotReady")); + } + + const code = bikeTypeToCode(bikeTypeText); + + const ackPromise = waitBikeTypeAck(code, 3000); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x05, code]).buffer, + { withoutResponse: false } + ); + + await new Promise((resolve) => setTimeout(resolve, 180)); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x06]).buffer, + { withoutResponse: false } + ); + + await ackPromise; + }); + }; + + const applyErgSmooth = async (ergSmoothText: string) => { + return enqueueFff3Operation(async () => { + if (!isConnected) { + throw new Error(t("info.deviceNotConnected")); + } + + if (!isFff3Ready) { + throw new Error(t("info3.deviceNotReady")); + } + + const code = ergSmoothToCode(ergSmoothText); + + const ackPromise = waitErgSmoothAck(code, 3000); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x03, code]).buffer, + { withoutResponse: false } + ); + + await new Promise((resolve) => setTimeout(resolve, 180)); + + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array([0x04]).buffer, + { withoutResponse: false } + ); + + await ackPromise; + }); + }; + + /* eslint-disable react-hooks/exhaustive-deps */ + useEffect(() => { + mountedRef.current = true; + + const connectionHandler = async (ev: { + peripheral: ScannedPeripheral; + connectionStatus: ConnectionStatus; + }) => { + const addr = ev.peripheral.address || ev.peripheral.systemId; + if (addr !== deviceKey) return; + + setIsConnected( + ev.connectionStatus === "connected" || ev.connectionStatus === "ready" + ); + setIsFtmsSessionReady(ev.connectionStatus === "ready"); + + const connected = + ev.connectionStatus === "connected" || ev.connectionStatus === "ready"; + + if (connected) { + hasConnectedOnceRef.current = true; + } + + if (ev.connectionStatus !== "ready") { + setIsFff3Ready(false); + notifySubscribedRef.current = false; + } + + if (ev.connectionStatus === "ready" && !notifySubscribedRef.current) { + await subscribeFff3IfNeeded(); + } + + if ( + !connected && + hasConnectedOnceRef.current && + mountedRef.current && + !disconnectingRef.current && + !hasNavigatedBackRef.current + ) { + hasNavigatedBackRef.current = true; + skipDisconnectOnLeaveRef.current = true; + + Alert.alert(t("common.notice"), t("info2.disconnectedNeedReconnect"), [ + { + text: t("info.confirm"), + onPress: () => { + navigation.reset({ + index: 0, + routes: [{ name: "ScanScreen3" }], + }); + }, + }, + ]); + } + }; + + Central.addListener("peripheralConnectionStatus", connectionHandler); + return () => { + mountedRef.current = false; + Central.removeListener("peripheralConnectionStatus", connectionHandler); + notifySubscribedRef.current = false; + setIsFff3Ready(false); + clearFff3Waiter(); + }; + }, [deviceKey, navigation, peripheral, t]); + /* eslint-enable react-hooks/exhaustive-deps */ + + useEffect(() => { + const unsubscribe = navigation.addListener("beforeRemove", async (e) => { + if (skipDisconnectOnLeaveRef.current) return; + if (disconnectingRef.current) return; + disconnectingRef.current = true; + + const next = e.data.action?.type; + const action = e.data.action as any; + + if ( + next === "NAVIGATE" && + (action?.payload?.name === "Dfu" || + action?.payload?.name === "Spindown") + ) { + disconnectingRef.current = false; + return; + } + + try { + await Central.disconnectPeripheral(peripheral); + } catch (err) { + console.warn("断开失败:", err); + } finally { + disconnectingRef.current = false; + } + }); + + return unsubscribe; + }, [navigation, peripheral]); + + useEffect(() => { + (async () => { + setIsLoading(true); + try { + await Central.connectPeripheral(peripheral); + await new Promise((resolve) => setTimeout(resolve, 500)); + setIsFtmsSessionReady(true); + + const readStr = async (srv: string, char: string) => { + try { + const v = await Central.readCharacteristic( + peripheral, + fullUUID(srv), + fullUUID(char) + ); + return v ? String.fromCharCode(...(v as any)) : t("common.unknown"); + } catch { + return t("common.unknown"); + } + }; + + setSerial(await readStr("180a", "2a25")); + setFirmware(await readStr("180a", "2a28")); + setHardware(await readStr("180a", "2a27")); + + try { + const v = await Central.readCharacteristic( + peripheral, + fullUUID("180f"), + fullUUID("2a19") + ); + if (v && (v as any).length) setBattery(`${(v as any)[0]}%`); + else setBattery(t("common.unknown")); + } catch { + setBattery(t("common.unknown")); + } + } catch (e) { + console.warn("读取失败", e); + setIsFtmsSessionReady(false); + Alert.alert(t("common.notice"), t("info3.connectReadFailed")); + } finally { + setIsLoading(false); + } + })(); + }, [peripheral, t]); + + /* eslint-disable react-hooks/exhaustive-deps */ + useEffect(() => { + if (!isConnected || !isFff3Ready) return; + + let cancelled = false; + + (async () => { + if (!cancelled) { + setIsInitializingSettings(true); + } + + try { + await new Promise((resolve) => setTimeout(resolve, 250)); + const currentTrim = await requestCurrentPowerTrim(3000); + if (!cancelled) { + const trimStr = deviceValueToPowerTrimText(currentTrim); + setPowerTrim(trimStr); + setPendingPowerTrim(trimStr); + } + } catch (err) { + console.warn("读取当前功率微调失败:", err); + } + + try { + await new Promise((resolve) => setTimeout(resolve, 120)); + const currentWeightValue = await requestCurrentWeight(3000); + if (!cancelled) { + const weightStr = deviceValueToWeightText(currentWeightValue); + setWeight(weightStr); + setPendingWeight(weightStr); + } + } catch (err) { + console.warn("读取当前体重失败:", err); + } + + try { + await new Promise((resolve) => setTimeout(resolve, 120)); + const currentUsedHours = await requestUsedHours(3000); + if (!cancelled) { + setUsedMileage(`${currentUsedHours.toFixed(1)}km`); + } + } catch (err) { + console.warn("读取使用时长失败:", err); + if (!cancelled) { + setUsedMileage(t("info3.readFailed")); + } + } + + try { + await new Promise((resolve) => setTimeout(resolve, 120)); + const currentBikeTypeCode = await requestCurrentBikeType(3000); + if (!cancelled) { + setBikeType(codeToBikeType(currentBikeTypeCode)); + } + } catch (err) { + console.warn("读取当前车型失败:", err); + } + + try { + await new Promise((resolve) => setTimeout(resolve, 120)); + const currentErgSmoothCode = await requestCurrentErgSmooth(3000); + if (!cancelled) { + setErgSmooth(codeToErgSmooth(currentErgSmoothCode)); + } + } catch (err) { + console.warn("读取当前ERG功率平滑失败:", err); + } finally { + if (!cancelled) { + setIsInitializingSettings(false); + } + } + })(); + + return () => { + cancelled = true; + setIsInitializingSettings(false); + }; + }, [isConnected, isFff3Ready, t]); + /* eslint-enable react-hooks/exhaustive-deps */ + + useEffect(() => { + if (!peripheral || !isFtmsSessionReady) return; + + let cancelled = false; + let cleanup: (() => void) | null = null; + + observeFtmsIndoorBikeData(peripheral, (data) => { + if (cancelled) return; + setSpeedKph(data.speedKph); + setCadence(data.cadence); + setPower(data.power); + }) + .then((unsubscribe) => { + if (cancelled) { + unsubscribe(); + return; + } + cleanup = unsubscribe; + }) + .catch((err) => { + console.warn("订阅共享 2AD2 失败", err); + }); + + return () => { + cancelled = true; + cleanup?.(); + }; + }, [peripheral, isFtmsSessionReady]); + + const goToDfu = () => { + if (!isConnected) { + Alert.alert(t("common.notice"), t("info2.disconnectedNeedReconnect"), [ + { + text: t("info.confirm"), + onPress: () => { + if (navigation.canGoBack()) { + navigation.goBack(); + } + }, + }, + ]); + return; + } + + skipDisconnectOnLeaveRef.current = true; + navigation.navigate("Dfu", { + deviceId: String(deviceKey), + systemId: peripheral.systemId, + address: peripheral.address, + name: peripheral.name ?? "", + firmware: firmware ?? "", + }); + }; + + useEffect(() => { + return () => { + clearFff3Waiter(); + }; + }, []); + + const startPendingActionFlow = async (action: PendingAction) => { + if (!action) return; + + setConfirmModalVisible(false); + setPendingAction(action); + + if (action === "weight") { + setProgressText(t("info3.settingWeight")); + setProgressModalVisible(true); + + try { + await applyWeight(pendingWeight); + setWeight(pendingWeight.trim()); + setPendingWeight(pendingWeight.trim()); + setProgressModalVisible(false); + Alert.alert(t("common.notice"), t("info3.weightSetDone")); + } catch (err: any) { + setProgressModalVisible(false); + Alert.alert( + t("common.notice"), + err?.message || t("info3.weightSetFailed") + ); + } finally { + setPendingAction(null); + } + return; + } + + setProgressText(t("info3.settingPowerTrim")); + setProgressModalVisible(true); + + try { + await applyPowerTrim(pendingPowerTrim); + setPowerTrim(pendingPowerTrim.trim()); + setPendingPowerTrim(pendingPowerTrim.trim()); + setProgressModalVisible(false); + Alert.alert(t("common.notice"), t("info3.powerTrimSetDone")); + } catch (err: any) { + setProgressModalVisible(false); + Alert.alert( + t("common.notice"), + err?.message || t("info3.powerTrimSetFailed") + ); + } finally { + setPendingAction(null); + } + }; + + const onPressWeightSave = () => { + Keyboard.dismiss(); + const nextValue = pendingWeight.trim(); + + if (!nextValue || nextValue === weight) { + setPendingWeight(weight); + return; + } + + if (!isValidWeightInput(nextValue)) { + Alert.alert(t("common.notice"), t("info3.invalidWeight")); + return; + } + + setPendingAction("weight"); + setConfirmModalVisible(true); + }; + + const onPressPowerTrimSave = () => { + Keyboard.dismiss(); + const nextValue = pendingPowerTrim.trim(); + + if (!nextValue || nextValue === powerTrim) { + setPendingPowerTrim(powerTrim); + return; + } + + if (!isValidPowerTrimInput(nextValue)) { + Alert.alert(t("common.notice"), t("info3.powerTrimRangeError")); + return; + } + + setPendingAction("powerTrim"); + setConfirmModalVisible(true); + }; + + const closeConfirmModal = () => { + setConfirmModalVisible(false); + setPendingAction(null); + }; + + const renderHelp = (key: Exclude) => { + return ( + setVisibleTooltip(key)} + onPressOut={() => setVisibleTooltip(null)} + style={styles.helpWrap} + > + + ? + + {visibleTooltip === key && ( + + {t("info3.helpText")} + + )} + + ); + }; + + const renderStatusTag = (text: string, variant: "pending" | "active") => { + return ( + + + {text} + + + ); + }; + + const renderDropdown = ( + key: Exclude, + value: string, + options: string[], + onSelect: (value: string) => void | Promise, + disabled = false, + loading = false + ) => { + const isOpen = openDropdown === key; + + return ( + + { + if (!isFff3Ready || disabled) return; + Keyboard.dismiss(); + setDropdownOptions(options); + setDropdownTitle( + key === "bikeType" ? t("info3.bikeType") : t("info3.ergSmooth") + ); + setDropdownOnSelect(() => onSelect); + setOpenDropdown(isOpen ? null : key); + }} + disabled={!isFff3Ready || disabled} + > + + {value} + + + {loading ? ( + + ) : ( + + )} + + + ); + }; + + return ( + { + Keyboard.dismiss(); + setOpenDropdown(null); + }} + > + + + + + + + {t("info.bluetoothName")}: + + {peripheral.name || t("common.unknownDevice")} + + + + + {t("info.idNumber")}: + {serial} + + + + {t("info.firmwareVersion")}: + {firmware} + + + + {t("info3.usedMileage")}: + {usedMileage} + + + + {t("info.connectionStatus")}: + + {isConnected + ? isFff3Ready + ? t("info.connected") + : t("info3.connecting") + : t("info.disconnected")} + + + + + + + {t("info.power")} + {power} + + + + + {t("info.cadence")} + {cadence} + + + + + {t("info3.speedKph")} + {speedKph} + + + + + + + + {t("info3.weightSetting")} + {renderHelp("weight")} + + + {t("info3.currentWeightValue", { value: weight })} + + {hasUnsavedWeight && + renderStatusTag(t("info3.pendingTag"), "pending")} + + + + + kg + + + + + + + + + + + + + + {t("info3.powerTrim")} + {renderHelp("trim")} + + + {t("info3.currentPowerTrimValue", { value: powerTrim })} + + {hasUnsavedPowerTrim && + renderStatusTag(t("info3.pendingTag"), "pending")} + + + + + % + + + + + + + + + + + + + + {t("info3.bikeType")} + {renderHelp("bikeType")} + + + {t("info3.currentTextValue", { value: bikeType })} + + {isSettingBikeType && + renderStatusTag(t("info3.settingTag"), "active")} + + + {renderDropdown( + "bikeType", + bikeType, + [ + t("info3.bikeTypeFollow"), + t("info3.bikeTypeRoad"), + t("info3.bikeTypeMtb26"), + t("info3.bikeTypeMtb275"), + t("info3.bikeTypeMtb29"), + t("info3.bikeTypeSmallWheel"), + ], + async (value) => { + try { + setIsSettingBikeType(true); + await applyBikeType(value); + setBikeType(value); + } catch (err: any) { + Alert.alert( + t("common.notice"), + err?.message || t("info3.bikeTypeSetFailed") + ); + } finally { + setIsSettingBikeType(false); + } + }, + isInitializingSettings || isSettingBikeType, + isInitializingSettings || isSettingBikeType + )} + + + + + + + + + {t("info3.ergSmooth")} + {renderHelp("ergSmooth")} + + + {t("info3.currentTextValue", { value: ergSmooth })} + + {isSettingErgSmooth && + renderStatusTag(t("info3.settingTag"), "active")} + + + {renderDropdown( + "ergSmooth", + ergSmooth, + [t("info3.ergOn"), t("info3.ergOff")], + async (value) => { + try { + setIsSettingErgSmooth(true); + await applyErgSmooth(value); + setErgSmooth(value); + } catch (err: any) { + Alert.alert( + t("common.notice"), + err?.message || t("info3.ergSmoothSetFailed") + ); + } finally { + setIsSettingErgSmooth(false); + } + }, + isInitializingSettings || isSettingErgSmooth, + isInitializingSettings || isSettingErgSmooth + )} + + + + + + + {t("info.firmwareUpgrade")} + + + + navigation.navigate("Spindown", { peripheral })} + > + {t("spindown.title")} + + + + + + + + {pendingAction === "weight" + ? t("info3.confirmWeightChange", { + value: pendingWeight.trim(), + }) + : t("info3.confirmPowerTrimChange", { + value: pendingPowerTrim.trim(), + })} + + + + + {t("common.no")} + + + startPendingActionFlow(pendingAction)} + > + + {t("common.yes")} + + + + + + + + {}} + > + + + + {progressText} + + + + + setOpenDropdown(null)} + > + setOpenDropdown(null)} + > + e.stopPropagation()} + > + {dropdownTitle} + + + {dropdownOptions.map((item) => ( + { + const handler = dropdownOnSelect; + setOpenDropdown(null); + if (handler) { + await handler(item); + } + }} + > + {item} + + ))} + + + + + + {isLoading && ( + + + {t("info.readingInfo")} + + )} + + + ); +} + +const RED = "#eb3b3b"; +const BG = "#ffffff"; +const DARK = "#333333"; +const ORANGE = "#f59e0b"; +const ORANGE_BG = "#fff7e6"; + +const styles = StyleSheet.create({ + screen: { + flex: 1, + backgroundColor: BG, + }, + scrollContent: { + paddingHorizontal: 18, + paddingBottom: 28, + backgroundColor: BG, + }, + card: { + backgroundColor: "#ffffff", + marginTop: 8, + }, + infoRow: { + flexDirection: "row", + alignItems: "center", + borderBottomWidth: 1.5, + borderBottomColor: RED, + paddingVertical: 8, + }, + infoLabel: { + fontSize: 16, + color: DARK, + fontWeight: "500", + minWidth: 92, + }, + infoValue: { + flex: 1, + fontSize: 16, + color: "#4a4a4a", + fontWeight: "500", + }, + metricsWrap: { + flexDirection: "row", + justifyContent: "center", + gap: 12, + paddingVertical: 12, + borderBottomWidth: 1.5, + borderBottomColor: RED, + marginBottom: 8, + }, + metricCard: { + width: 104, + height: 104, + borderRadius: 20, + borderWidth: 2, + borderColor: RED, + alignItems: "center", + justifyContent: "center", + backgroundColor: "#ffffff", + }, + metricLabel: { + fontSize: 14, + color: DARK, + marginTop: 6, + }, + metricValue: { + fontSize: 24, + fontWeight: "700", + color: DARK, + marginTop: 6, + }, + formBlock: { + borderBottomWidth: 1.5, + borderBottomColor: RED, + paddingVertical: 8, + }, + formBlockLast: { + borderBottomWidth: 1.5, + borderBottomColor: RED, + paddingVertical: 8, + }, + formRowInline: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + gap: 10, + }, + formLabelColumn: { + width: 118, + justifyContent: "center", + }, + formLabelWrap: { + flexDirection: "row", + alignItems: "center", + }, + formLabel: { + fontSize: 15, + color: DARK, + marginRight: 6, + }, + formControlRow: { + width: 146, + flexDirection: "row", + alignItems: "center", + gap: 6, + justifyContent: "flex-start", + }, + currentValueBelow: { + marginTop: 4, + fontSize: 13, + color: "#777", + fontWeight: "500", + }, + statusTag: { + marginTop: 4, + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 999, + }, + statusTagPending: { + backgroundColor: ORANGE_BG, + }, + statusTagActive: { + backgroundColor: "#ffe9e9", + }, + statusTagText: { + fontSize: 12, + fontWeight: "700", + }, + statusTagPendingText: { + color: ORANGE, + }, + statusTagActiveText: { + color: RED, + }, + helpWrap: { + position: "relative", + }, + helpDot: { + width: 24, + height: 24, + borderRadius: 12, + backgroundColor: RED, + alignItems: "center", + justifyContent: "center", + }, + helpDotText: { + color: "white", + fontSize: 16, + fontWeight: "700", + lineHeight: 18, + }, + tooltipBubble: { + position: "absolute", + left: -8, + bottom: 32, + backgroundColor: "rgba(0,0,0,0.82)", + paddingHorizontal: 10, + paddingVertical: 7, + borderRadius: 8, + minWidth: 110, + zIndex: 99999, + elevation: 99999, + }, + tooltipText: { + color: "#ffffff", + fontSize: 12, + lineHeight: 16, + }, + inputWrapWithButton: { + width: "100%", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + gap: 8, + }, + inputWrap: { + width: 106, + height: 36, + borderWidth: 1.5, + borderColor: "#666", + backgroundColor: "#ffffff", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: 10, + }, + inputWrapPending: { + borderColor: ORANGE, + backgroundColor: ORANGE_BG, + }, + inputTextCenter: { + flex: 1, + fontSize: 16, + color: DARK, + textAlign: "center", + fontWeight: "700", + paddingVertical: 0, + }, + unitText: { + fontSize: 14, + color: "#666", + marginLeft: 6, + }, + inlineSaveButton: { + width: 34, + height: 34, + borderRadius: 8, + backgroundColor: RED, + alignItems: "center", + justifyContent: "center", + }, + inlineSaveButtonDisabled: { + backgroundColor: "#c9c9c9", + }, + dropdownWrap: { + width: 146, + }, + selectBox: { + width: 146, + height: 36, + borderWidth: 1.5, + borderColor: "#666", + backgroundColor: "#ffffff", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: 10, + }, + selectBoxDisabled: { + backgroundColor: "#f6f6f6", + borderColor: "#cfcfcf", + }, + selectText: { + flex: 1, + fontSize: 15, + color: DARK, + fontWeight: "600", + marginRight: 8, + }, + selectTextDisabled: { + color: "#999", + }, + upgradeButton: { + marginTop: 16, + height: 44, + borderRadius: 8, + backgroundColor: RED, + alignItems: "center", + justifyContent: "center", + }, + rotateCancelButton: { + marginTop: 10, + height: 44, + borderRadius: 8, + backgroundColor: RED, + alignItems: "center", + justifyContent: "center", + }, + upgradeButtonText: { + color: "white", + fontSize: 20, + fontWeight: "700", + }, + loadingWrap: { + alignItems: "center", + justifyContent: "center", + marginTop: 24, + }, + loadingText: { + marginTop: 10, + fontSize: 15, + color: DARK, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.35)", + alignItems: "center", + justifyContent: "center", + paddingHorizontal: 28, + }, + confirmModalBox: { + width: "100%", + backgroundColor: "#ffffff", + borderRadius: 14, + paddingHorizontal: 18, + paddingTop: 22, + paddingBottom: 16, + }, + confirmModalText: { + fontSize: 17, + color: DARK, + textAlign: "center", + lineHeight: 24, + fontWeight: "500", + }, + confirmButtonRow: { + flexDirection: "row", + justifyContent: "space-between", + marginTop: 20, + gap: 12, + }, + modalBtn: { + flex: 1, + height: 42, + borderRadius: 8, + alignItems: "center", + justifyContent: "center", + }, + modalBtnGhost: { + backgroundColor: "#f2f2f2", + }, + modalBtnPrimary: { + backgroundColor: RED, + }, + modalBtnGhostText: { + color: DARK, + fontSize: 16, + fontWeight: "600", + }, + modalBtnPrimaryText: { + color: "#ffffff", + fontSize: 16, + fontWeight: "600", + }, + progressModalBox: { + width: 180, + backgroundColor: "#ffffff", + borderRadius: 14, + paddingVertical: 22, + paddingHorizontal: 18, + alignItems: "center", + justifyContent: "center", + }, + progressModalText: { + marginTop: 12, + fontSize: 16, + color: DARK, + fontWeight: "500", + textAlign: "center", + }, + dropdownModalBox: { + width: "86%", + maxHeight: "60%", + backgroundColor: "#fff", + borderRadius: 14, + paddingTop: 16, + paddingBottom: 10, + paddingHorizontal: 14, + }, + dropdownModalTitle: { + fontSize: 17, + color: DARK, + fontWeight: "700", + textAlign: "center", + marginBottom: 12, + }, + dropdownScroll: { + maxHeight: 320, + }, + dropdownScrollContent: { + paddingBottom: 6, + }, + dropdownModalItem: { + minHeight: 46, + borderBottomWidth: 1, + borderBottomColor: "#ececec", + justifyContent: "center", + paddingHorizontal: 10, + paddingVertical: 10, + }, + dropdownModalItemText: { + fontSize: 16, + color: DARK, + }, +}); diff --git a/src/ScanScreen.tsx b/src/ScanScreen.tsx index 782f7e5..6214707 100644 --- a/src/ScanScreen.tsx +++ b/src/ScanScreen.tsx @@ -1,4 +1,4 @@ -// src/ScanScreen.tsx +// src/ScanScreen.tsx此页面为功率计搜索页面 import React, { useEffect, useState, useCallback, useRef } from "react"; import { View, @@ -76,7 +76,7 @@ export default function ScanScreen({ navigation }: Props) { // 更新或添加设备,并记录最后扫描时间 const updatePeripherals = useCallback((p: ScannedPeripheral) => { - if (!p?.name || !p.name.startsWith("POWERFUN")) return; + if (!p?.name || (!p.name.startsWith("POWERFUN") && !p.name.startsWith("PF-PM5"))) return; if ((p.advertisementData?.rssi ?? -999) < -90) return; // 已过滤弱信号 setDevices((prev) => { @@ -228,4 +228,4 @@ const styles = StyleSheet.create({ }, emptyText: { fontSize: 18, fontWeight: "600", marginBottom: 8 }, tips: { fontSize: 14, color: "#888", textAlign: "center" }, -}); +}); \ No newline at end of file diff --git a/src/ScanScreen2.tsx b/src/ScanScreen2.tsx new file mode 100644 index 0000000..df06ec7 --- /dev/null +++ b/src/ScanScreen2.tsx @@ -0,0 +1,238 @@ +// src/ScanScreen.tsx此页面为桨频器搜索页面 +import React, { useEffect, useState, useCallback, useRef } from "react"; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + RefreshControl, +} from "react-native"; +import { + Central, + CentralEventMap, + ScannedPeripheral, +} from "@systemic-games/react-native-bluetooth-le"; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import { RootStackParamList } from "../App"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; // 信号图标集 + +type Props = NativeStackScreenProps; +type DeviceWithTimestamp = ScannedPeripheral & { lastSeen: number }; +import { useTranslation } from 'react-i18next'; +import MyStatusbar from "./component/MyStatusbar"; +import MyHeader from "./component/MyHeader"; + +export default function ScanScreen2({ navigation }: Props) { + const [devices, setDevices] = useState([]); + const [scanning, setScanning] = useState(false); + const [refreshing, setRefreshing] = useState(false); + + const handlerRef = useRef<((payload: CentralEventMap["scannedPeripheral"]) => void) | null>(null); + const { t } = useTranslation(); + // 将 RSSI 转换为信号格数(0-4) + const getSignalLevel = (rssi?: number): number => { + if (rssi === undefined) return 0; + if (rssi >= -50) return 4; // 很强 + if (rssi >= -65) return 3; // 强 + if (rssi >= -80) return 2; // 一般 + if (rssi >= -90) return 1; // 弱 + return 0; // 无信号 + }; + + // 渲染信号图标(格数+颜色) + const renderSignalIcon = (rssi?: number) => { + const level = getSignalLevel(rssi); + + let iconColor = "#aaa"; // 默认灰色 + switch (level) { + case 4: + iconColor = "#8BC34A"; // 亮绿 + break; + case 3: + iconColor = "#4CAF50"; // 深绿 + break; + case 2: + iconColor = "#FF9800"; // 橙色 + break; + case 1: + iconColor = "#F44336"; // 红色 + break; + } + + const iconName = + level === 4 + ? "wifi-strength-4" + : level === 3 + ? "wifi-strength-3" + : level === 2 + ? "wifi-strength-2" + : level === 1 + ? "wifi-strength-1" + : "wifi-strength-outline"; + + return ; + }; + + // 更新或添加设备,并记录最后扫描时间 + const updatePeripherals = useCallback((p: ScannedPeripheral) => { + if (!p?.name || (!p.name.startsWith("POWERFUN") && !p.name.startsWith("PF-STK"))) return; + if ((p.advertisementData?.rssi ?? -999) < -90) return; // 已过滤弱信号 + + setDevices((prev) => { + const now = Date.now(); + const exists = prev.find((d) => d.systemId === p.systemId); + if (exists) { + return prev.map((d) => + d.systemId === p.systemId ? { ...p, lastSeen: now } : d + ); + } else { + return [...prev, { ...p, lastSeen: now }]; + } + }); + }, []); + + // 清理消失或信号低的设备 + useEffect(() => { + const interval = setInterval(() => { + const now = Date.now(); + setDevices((prev) => + prev.filter( + (d) => + (d.advertisementData?.rssi ?? -999) >= -90 && + now - d.lastSeen < 3000 + ) + ); + }, 1000); + return () => clearInterval(interval); + }, []); + + const stopScan = useCallback(() => { + setScanning(false); + try { Central.stopScan(); } catch {} + if (handlerRef.current) { + try { Central.removeListener("scannedPeripheral", handlerRef.current); } catch {} + handlerRef.current = null; + } + try { Central.shutdown(); } catch {} + }, []); + + const startScan = useCallback(() => { + stopScan(); + setDevices([]); + setScanning(true); + + const onScanned = (payload: CentralEventMap["scannedPeripheral"]) => { + const p = payload?.peripheral; + console.log("🔥 scanned raw peripheral =", JSON.stringify(p, null, 2)); + console.log("🔥 scanned raw peripheral keys =", p ? Object.keys(p) : []); + + if (p?.name && p.advertisementData?.isConnectable) { + updatePeripherals(p); + } + }; + handlerRef.current = onScanned; + + try { + Central.initialize(); + Central.addListener("scannedPeripheral", onScanned); + Central.startScan([]); + } catch (e) { + console.warn("Central scan start error:", e); + setScanning(false); + handlerRef.current = null; + try { Central.shutdown(); } catch {} + } + }, [stopScan, updatePeripherals]); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + stopScan(); + await new Promise((resolve) => setTimeout(resolve, 200)); + startScan(); + setRefreshing(false); + }, [stopScan, startScan]); + + useEffect(() => { + startScan(); + return () => { stopScan(); }; + }, [startScan, stopScan]); + + return ( + + + + item.systemId} + renderItem={({ item }) => ( + { + console.log("🔥 navigate item =", JSON.stringify(item, null, 2)); + console.log("🔥 navigate item keys =", Object.keys(item)); + navigation.navigate("Info2", { peripheral: item }); +}} + style={styles.deviceRow} + > + + {item.name || t("scan.noName")} + + {renderSignalIcon(item.advertisementData?.rssi)} + + {item.advertisementData?.rssi ?? "--"} dBm + + + + + )} + ListEmptyComponent={() => ( + + {scanning ? ( + <> + {t("paddleScan.scanning")} + {t("paddleScan.tipScanning")} + + ) : ( + <> + {t("paddleScan.noDevice")} + {t("paddleScan.tipBluetooth")} + + )} + + )} + refreshControl={} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: "#fff" }, + deviceRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingVertical: 15, + paddingHorizontal: 20, + borderBottomWidth: 1, + borderColor: "#eee", + }, + deviceInfo: { flexDirection: "column" }, + deviceName: { fontSize: 16, fontWeight: "500" }, + rssiRow: { flexDirection: "row", alignItems: "center", marginTop: 4 }, + rssiText: { fontSize: 14, color: "#666", marginLeft: 6 }, + emptyBox: { + flex: 1, + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 20, + marginTop: 50, + }, + emptyText: { fontSize: 18, fontWeight: "600", marginBottom: 8 }, + tips: { fontSize: 14, color: "#888", textAlign: "center" }, +}); diff --git a/src/ScanScreen3.tsx b/src/ScanScreen3.tsx new file mode 100644 index 0000000..a8ba184 --- /dev/null +++ b/src/ScanScreen3.tsx @@ -0,0 +1,231 @@ +// src/ScanScreen3.tsx此页面为T5骑行台搜索页面 +import React, { useEffect, useState, useCallback, useRef } from "react"; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + RefreshControl, +} from "react-native"; +import { + Central, + CentralEventMap, + ScannedPeripheral, +} from "@systemic-games/react-native-bluetooth-le"; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import { RootStackParamList } from "../App"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; // 信号图标集 + +type Props = NativeStackScreenProps; +type DeviceWithTimestamp = ScannedPeripheral & { lastSeen: number }; +import { useTranslation } from 'react-i18next'; +import MyStatusbar from "./component/MyStatusbar"; +import MyHeader from "./component/MyHeader"; + +export default function ScanScreen3({ navigation }: Props) { + const [devices, setDevices] = useState([]); + const [scanning, setScanning] = useState(false); + const [refreshing, setRefreshing] = useState(false); + + const handlerRef = useRef<((payload: CentralEventMap["scannedPeripheral"]) => void) | null>(null); + const { t } = useTranslation(); + // 将 RSSI 转换为信号格数(0-4) + const getSignalLevel = (rssi?: number): number => { + if (rssi === undefined) return 0; + if (rssi >= -50) return 4; // 很强 + if (rssi >= -65) return 3; // 强 + if (rssi >= -80) return 2; // 一般 + if (rssi >= -90) return 1; // 弱 + return 0; // 无信号 + }; + + // 渲染信号图标(格数+颜色) + const renderSignalIcon = (rssi?: number) => { + const level = getSignalLevel(rssi); + + let iconColor = "#aaa"; // 默认灰色 + switch (level) { + case 4: + iconColor = "#8BC34A"; // 亮绿 + break; + case 3: + iconColor = "#4CAF50"; // 深绿 + break; + case 2: + iconColor = "#FF9800"; // 橙色 + break; + case 1: + iconColor = "#F44336"; // 红色 + break; + } + + const iconName = + level === 4 + ? "wifi-strength-4" + : level === 3 + ? "wifi-strength-3" + : level === 2 + ? "wifi-strength-2" + : level === 1 + ? "wifi-strength-1" + : "wifi-strength-outline"; + + return ; + }; + + // 更新或添加设备,并记录最后扫描时间 + const updatePeripherals = useCallback((p: ScannedPeripheral) => { + if (!p?.name || (!p.name.startsWith("POWERFUN") && !p.name.startsWith("PF-T5"))) return; + if ((p.advertisementData?.rssi ?? -999) < -90) return; // 已过滤弱信号 + + setDevices((prev) => { + const now = Date.now(); + const exists = prev.find((d) => d.systemId === p.systemId); + if (exists) { + return prev.map((d) => + d.systemId === p.systemId ? { ...p, lastSeen: now } : d + ); + } else { + return [...prev, { ...p, lastSeen: now }]; + } + }); + }, []); + + // 清理消失或信号低的设备 + useEffect(() => { + const interval = setInterval(() => { + const now = Date.now(); + setDevices((prev) => + prev.filter( + (d) => + (d.advertisementData?.rssi ?? -999) >= -90 && + now - d.lastSeen < 3000 + ) + ); + }, 1000); + return () => clearInterval(interval); + }, []); + + const stopScan = useCallback(() => { + setScanning(false); + try { Central.stopScan(); } catch {} + if (handlerRef.current) { + try { Central.removeListener("scannedPeripheral", handlerRef.current); } catch {} + handlerRef.current = null; + } + try { Central.shutdown(); } catch {} + }, []); + + const startScan = useCallback(() => { + stopScan(); + setDevices([]); + setScanning(true); + + const onScanned = (payload: CentralEventMap["scannedPeripheral"]) => { + const p = payload?.peripheral; + if (p?.name && p.advertisementData?.isConnectable) { + updatePeripherals(p); + } + }; + handlerRef.current = onScanned; + + try { + Central.initialize(); + Central.addListener("scannedPeripheral", onScanned); + Central.startScan([]); + } catch (e) { + console.warn("Central scan start error:", e); + setScanning(false); + handlerRef.current = null; + try { Central.shutdown(); } catch {} + } + }, [stopScan, updatePeripherals]); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + stopScan(); + await new Promise((resolve) => setTimeout(resolve, 200)); + startScan(); + setRefreshing(false); + }, [stopScan, startScan]); + + useEffect(() => { + startScan(); + return () => { stopScan(); }; + }, [startScan, stopScan]); + + return ( + + + + item.systemId} + renderItem={({ item }) => ( + navigation.navigate("Info3", { peripheral: item })} + style={styles.deviceRow} + > + + {item.name || t("scan.noName")} + + {renderSignalIcon(item.advertisementData?.rssi)} + + {item.advertisementData?.rssi ?? "--"} dBm + + + + + )} + ListEmptyComponent={() => ( + + {scanning ? ( + <> + {t("t5Scan.scanning")} + {t("t5Scan.tipScanning")} + + ) : ( + <> + {t("t5Scan.noDevice")} + {t("t5Scan.tipBluetooth")} + + )} + + )} + refreshControl={} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: "#fff" }, + deviceRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingVertical: 15, + paddingHorizontal: 20, + borderBottomWidth: 1, + borderColor: "#eee", + }, + deviceInfo: { flexDirection: "column" }, + deviceName: { fontSize: 16, fontWeight: "500" }, + rssiRow: { flexDirection: "row", alignItems: "center", marginTop: 4 }, + rssiText: { fontSize: 14, color: "#666", marginLeft: 6 }, + emptyBox: { + flex: 1, + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 20, + marginTop: 50, + }, + emptyText: { fontSize: 18, fontWeight: "600", marginBottom: 8 }, + tips: { fontSize: 14, color: "#888", textAlign: "center" }, +}); diff --git a/src/SpindownScreen.tsx b/src/SpindownScreen.tsx new file mode 100644 index 0000000..9271c7e --- /dev/null +++ b/src/SpindownScreen.tsx @@ -0,0 +1,796 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { + ActivityIndicator, + Alert, + ScrollView, + StyleSheet, + TouchableOpacity, + View, + Text as RNText, +} from "react-native"; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; +import { + Central, + ConnectionStatus, + ScannedPeripheral, +} from "@systemic-games/react-native-bluetooth-le"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; +import { RootStackParamList } from "../App"; +import MyHeader from "./component/MyHeader"; +import MyStatusbar from "./component/MyStatusbar"; +import { useTranslation } from "react-i18next"; +import { observeFtmsIndoorBikeData } from "./helper/ftmsIndoorBikeDataBus"; + +type Props = NativeStackScreenProps; +type Phase = "reach36" | "wait18" | "calibrating" | "completed" | "error"; + +const powerServiceUuid = "fff1"; +const powerWriteUuid = "fff2"; +const powerNotifyUuid = "fff3"; + +const fullUUID = (uuid: string) => + uuid.length === 4 + ? `0000${uuid}-0000-1000-8000-00805f9b34fb` + : uuid; + +const Text = ({ children, style, ...props }: any) => { + return ( + + {children} + + ); +}; + +export default function SpindownScreen({ route, navigation }: Props) { + const { peripheral } = route.params; + const { t } = useTranslation(); + + const deviceKey = useMemo( + () => peripheral.address || peripheral.systemId, + [peripheral.address, peripheral.systemId] + ); + + const [speedKph, setSpeedKph] = useState(0); + const [isConnected, setIsConnected] = useState(false); + const [isFff3Ready, setIsFff3Ready] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [phase, setPhase] = useState("reach36"); + const [step1Done, setStep1Done] = useState(false); + const [step2Done, setStep2Done] = useState(false); + const [statusText, setStatusText] = useState( + t("spindown.statusReach36", { speed: 36 }) + ); + const [spindownSeconds, setSpindownSeconds] = useState(null); + + const notifySubscribedRef = useRef(false); + const spindownStartedRef = useRef(false); + const spindownModeEnabledRef = useRef(false); + const resolverRef = useRef<((value: Uint8Array) => void) | null>(null); + const rejecterRef = useRef<((reason?: any) => void) | null>(null); + const timeoutRef = useRef | null>(null); + const mountedRef = useRef(true); + const hasConnectedOnceRef = useRef(false); + const hasNavigatedBackRef = useRef(false); + const skipDisconnectOnLeaveRef = useRef(false); + const isConnectedRef = useRef(false); + const isFff3ReadyRef = useRef(false); + const spindownResultPromiseRef = useRef | null>(null); + const DEBUG = false; + const phaseRef = useRef("reach36"); +const lastUiUpdateRef = useRef(0); + +useEffect(() => { + phaseRef.current = phase; +}, [phase]); + + useEffect(() => { + isConnectedRef.current = isConnected; + }, [isConnected]); + + useEffect(() => { + isFff3ReadyRef.current = isFff3Ready; + }, [isFff3Ready]); + + // 新增:缓存最近收到的 0x09 消旋结果,避免结果包先到、waiter 后挂导致丢包 + const lastSpindownPacketRef = useRef(null); + + const bytesToHex = (bytes: Uint8Array) => + Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0").toUpperCase()) + .join(""); + + const clearWaiter = () => { + resolverRef.current = null; + rejecterRef.current = null; + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + + const formatSpindownSeconds = (seconds: number) => { + return seconds.toFixed(2).replace(/\.?0+$/, ""); + }; + +const subscribeFff3IfNeeded = async () => { + if (notifySubscribedRef.current) return; + + try { + await Central.unsubscribeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerNotifyUuid) + ).catch(() => {}); + + await Central.subscribeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerNotifyUuid), + (notifyEv) => { + try { + const raw = notifyEv.value; + if (!raw) return; + + const bytes = new Uint8Array(raw); + + if (DEBUG) { + console.log("[BLE] FFF3 =", bytes.length); + } + + if (bytes.length >= 3 && bytes[0] === 0x09) { + lastSpindownPacketRef.current = bytes; + } + + resolverRef.current?.(bytes); + } catch (err) { + rejecterRef.current?.(err); + clearWaiter(); + } + } + ); + + notifySubscribedRef.current = true; + isFff3ReadyRef.current = true; + setIsFff3Ready(true); + } catch { + isFff3ReadyRef.current = false; + setIsFff3Ready(false); + } +}; + + + const waitForSpindownTime = (timeout = 5000) => { + return new Promise((resolve, reject) => { + clearWaiter(); + + // 先吃缓存,防止 0x09 结果包在 runSpindown 前就已经到了 + const cached = lastSpindownPacketRef.current; + if (cached && cached.length >= 3 && cached[0] === 0x09) { + console.log("[BLE] use cached spindown packet =", bytesToHex(cached)); + + const rawSeconds = cached[1] | (cached[2] << 8); + lastSpindownPacketRef.current = null; + + if (rawSeconds === 0xffff) { + reject(new Error(t("spindown.failedRetry"))); + return; + } + + resolve(rawSeconds / 100); + return; + } + + resolverRef.current = (value: Uint8Array) => { + if (value.length < 3 || value[0] !== 0x09) { + console.log("[BLE] ignore unrelated FFF3(spindown):", bytesToHex(value)); + return; + } + + lastSpindownPacketRef.current = null; + + const rawSeconds = value[1] | (value[2] << 8); + if (rawSeconds === 0xffff) { + clearWaiter(); + reject(new Error(t("spindown.failedRetry"))); + return; + } + + const displaySeconds = rawSeconds / 100; + clearWaiter(); + resolve(displaySeconds); + }; + + rejecterRef.current = reject; + timeoutRef.current = setTimeout(() => { + clearWaiter(); + reject(new Error(t("spindown.timeout"))); + }, timeout); + }); + }; + + const prepareSpindownResultWait = (timeout = 45000) => { + if (spindownResultPromiseRef.current) { + return spindownResultPromiseRef.current; + } + + const promise = waitForSpindownTime(timeout); + spindownResultPromiseRef.current = promise; + void promise.catch(() => {}); + return promise; + }; + + const writeFff2 = async (bytes: number[]) => { + await Central.writeCharacteristic( + peripheral, + fullUUID(powerServiceUuid), + fullUUID(powerWriteUuid), + new Uint8Array(bytes).buffer, + { withoutResponse: false } + ); + }; + + const enableSpindownMode = async () => { + if (spindownModeEnabledRef.current) return; + if (!isConnectedRef.current || !isFff3ReadyRef.current) return; + + // 开始一轮新的消旋前清掉旧缓存,避免上一次结果污染 + lastSpindownPacketRef.current = null; + + console.log("[BLE] write FFF2 1001"); + await writeFff2([0x10, 0x01]); + spindownModeEnabledRef.current = true; + }; + + const disableSpindownMode = async () => { + if (!spindownModeEnabledRef.current) return; + + try { + console.log("[BLE] write FFF2 1000"); + await writeFff2([0x10, 0x00]); + } finally { + spindownModeEnabledRef.current = false; + } + }; + + const runSpindown = async () => { + if (spindownStartedRef.current) return; + spindownStartedRef.current = true; + + try { + if (!isConnectedRef.current || !isFff3ReadyRef.current) { + throw new Error(t("spindown.deviceNotReady")); + } + + setStatusText(t("spindown.statusCalibrating")); + const seconds = await prepareSpindownResultWait(5000); + setSpindownSeconds(seconds); + setPhase("completed"); + phaseRef.current = "completed"; + setStatusText(t("spindown.statusCompleted")); + } catch (err: any) { + console.warn("消旋失败:", err); + setPhase("error"); + phaseRef.current = "error"; + setStatusText("消旋失败,请重新消旋"); + Alert.alert( + "消旋失败", + err?.message || "本次消旋未完成,请检查速度是否符合要求后重新尝试。", + [{ text: "确定" }] + ); + } finally { + spindownResultPromiseRef.current = null; + } + }; + + useEffect(() => { + mountedRef.current = true; + + const connectionHandler = async (ev: { + peripheral: ScannedPeripheral; + connectionStatus: ConnectionStatus; + }) => { + const addr = ev.peripheral.address || ev.peripheral.systemId; + if (addr !== deviceKey) return; + + const connected = + ev.connectionStatus === "connected" || ev.connectionStatus === "ready"; + + isConnectedRef.current = connected; + setIsConnected(connected); + + if (connected) { + hasConnectedOnceRef.current = true; + } + + if (ev.connectionStatus !== "ready") { + isFff3ReadyRef.current = false; + setIsFff3Ready(false); + notifySubscribedRef.current = false; + } + + if (ev.connectionStatus === "ready" && !notifySubscribedRef.current) { + await subscribeFff3IfNeeded(); + } + + if ( + !connected && + hasConnectedOnceRef.current && + mountedRef.current && + !hasNavigatedBackRef.current + ) { + hasNavigatedBackRef.current = true; + skipDisconnectOnLeaveRef.current = true; + + Alert.alert(t("common.notice"), t("info2.disconnectedNeedReconnect"), [ + { + text: t("info.confirm"), + onPress: () => { + navigation.reset({ + index: 0, + routes: [{ name: "ScanScreen3" }], + }); + }, + }, + ]); + } + }; + + Central.addListener("peripheralConnectionStatus", connectionHandler); + return () => { + mountedRef.current = false; + Central.removeListener("peripheralConnectionStatus", connectionHandler); + notifySubscribedRef.current = false; + setIsFff3Ready(false); + clearWaiter(); + }; + }, [deviceKey, navigation, peripheral, t]); + + useEffect(() => { + let cancelled = false; + + (async () => { + setIsLoading(true); + try { + // 进入页面时清一次缓存,避免旧结果残留 + lastSpindownPacketRef.current = null; + spindownResultPromiseRef.current = null; + spindownStartedRef.current = false; + phaseRef.current = "reach36"; + + // Reuse existing connection from Info3 instead of reconnecting here. + isConnectedRef.current = true; + setIsConnected(true); + await subscribeFff3IfNeeded(); + } catch (err) { + console.warn("连接设备失败:", err); + isConnectedRef.current = false; + setIsConnected(false); + if (!cancelled) { + Alert.alert(t("common.notice"), t("spindown.connectFailed")); + } + } finally { + if (!cancelled) { + setIsLoading(false); + } + } + })(); + + return () => { + cancelled = true; + if (skipDisconnectOnLeaveRef.current) return; + disableSpindownMode().catch((err) => { + console.warn("关闭消旋模式失败:", err); + }); + }; + }, [peripheral, t]); + + useEffect(() => { + if (!isConnected || !isFff3Ready) return; + + enableSpindownMode().catch((err) => { + console.warn("启用消旋模式失败:", err); + spindownModeEnabledRef.current = false; + }); + }, [isConnected, isFff3Ready]); + + useEffect(() => { + if (!peripheral) return; + + let cancelled = false; + let cleanup: (() => void) | null = null; + + observeFtmsIndoorBikeData(peripheral, (data) => { + if (cancelled) return; + + const speed = data.speedKph; + const now = Date.now(); + + // ✅ UI 限速 10Hz + if (now - lastUiUpdateRef.current > 100) { + lastUiUpdateRef.current = now; + setSpeedKph(speed); + } + + // ===== ⭐ 实时状态机(不再依赖 React state) ===== + const currentPhase = phaseRef.current; + + // step1:到 36 + if (currentPhase === "reach36" && speed >= 36) { + phaseRef.current = "wait18"; + setPhase("wait18"); + setStep1Done(true); + setStatusText(t("spindown.statusReached36", { high: 36, low: 18 })); + prepareSpindownResultWait(); + return; + } + + // step2:降到 18 + if (currentPhase === "wait18" && speed <= 18) { + phaseRef.current = "calibrating"; + setPhase("calibrating"); + setStep2Done(true); + setStatusText(t("spindown.statusCalibrating")); + + runSpindown(); + return; + } + }) + .then((unsubscribe) => { + if (cancelled) { + unsubscribe(); + return; + } + cleanup = unsubscribe; + }) + .catch(() => {}); + + return () => { + cancelled = true; + cleanup?.(); + }; +}, [peripheral, t]); + + const renderStepState = (done: boolean, active: boolean) => { + if (done) { + return ; + } + + if (active) { + return ; + } + + return ; + }; + + const isDeviceReady = isConnected || isFff3Ready; + const connectionLabel = isDeviceReady + ? t("spindown.connected") + : t("spindown.connecting"); + const connectionColor = isDeviceReady ? "#19a15f" : "#eb3b3b"; + + return ( + + + + + + + + + + + {connectionLabel} + + + + + {t("spindown.headerTitle")} + {statusText} + + + {t("spindown.targetLabel")} + 36 km/h + {t("spindown.targetHint")} + + + + {t("spindown.currentSpeed")} + {speedKph.toFixed(1)} km/h + + + + + + + 1 + + + + {t("spindown.step1Title", { speed: 36 })} + + {t("spindown.step1Desc")} + + {renderStepState(step1Done, phase === "reach36")} + + + + + + + 2 + + + {t("spindown.step2Title")} + + {t("spindown.step2Desc", { speed: 18 })} + + + {renderStepState(step2Done, phase === "wait18" || phase === "calibrating")} + + + + + + + 3 + + + {t("spindown.step3Title")} + + {phase === "error" + ? "消旋失败,请重新消旋" + : spindownSeconds === null + ? phase === "calibrating" + ? t("spindown.step3Loading") + : t("spindown.step3Pending") + : `${formatSpindownSeconds(spindownSeconds)}s`} + + + {phase === "error" ? ( + + ) : ( + renderStepState(phase === "completed", phase === "calibrating") + )} + + + + {isLoading && ( + + + {t("spindown.loading")} + + )} + + {phase === "completed" && ( + + + + {t("spindown.result", { + seconds: formatSpindownSeconds(spindownSeconds ?? 0), + })} + + + )} + + {phase === "error" && ( + navigation.replace("Spindown", { peripheral })} + > + {t("spindown.retry")} + + )} + + + ); +} + +const RED = "#eb3b3b"; +const BG = "#f6f7fb"; +const DARK = "#242424"; + +const styles = StyleSheet.create({ + screen: { + flex: 1, + backgroundColor: BG, + }, + scrollContent: { + paddingHorizontal: 18, + paddingTop: 10, + paddingBottom: 28, + }, + heroCard: { + backgroundColor: "#ffffff", + borderRadius: 24, + paddingHorizontal: 18, + paddingVertical: 18, + shadowColor: "#000", + shadowOpacity: 0.06, + shadowRadius: 12, + shadowOffset: { width: 0, height: 6 }, + elevation: 3, + }, + connectionRow: { + flexDirection: "row", + justifyContent: "flex-end", + }, + connectionBadge: { + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 999, + }, + connectionDot: { + width: 8, + height: 8, + borderRadius: 4, + marginRight: 6, + }, + connectionText: { + fontSize: 13, + fontWeight: "600", + }, + heroTitle: { + fontSize: 18, + fontWeight: "800", + color: DARK, + marginTop: 10, + }, + heroHint: { + fontSize: 15, + color: "#666666", + lineHeight: 22, + marginTop: 8, + minHeight: 26, + }, + targetCard: { + marginTop: 12, + borderRadius: 18, + backgroundColor: "#fff0f0", + borderWidth: 1, + borderColor: "#ffd4d4", + paddingVertical: 14, + paddingHorizontal: 14, + alignItems: "center", + }, + targetLabel: { + fontSize: 14, + color: "#8a5555", + fontWeight: "600", + }, + targetValue: { + marginTop: 4, + fontSize: 36, + color: RED, + fontWeight: "900", + lineHeight: 40, + }, + targetHint: { + marginTop: 4, + fontSize: 13, + color: "#9b6c6c", + }, + speedCard: { + marginTop: 18, + borderRadius: 22, + backgroundColor: "#fff5f5", + borderWidth: 1, + borderColor: "#ffdede", + paddingVertical: 20, + alignItems: "center", + }, + speedLabel: { + fontSize: 15, + color: "#666666", + }, + speedValue: { + fontSize: 34, + fontWeight: "800", + color: RED, + marginTop: 8, + }, + stepCard: { + marginTop: 16, + backgroundColor: "#ffffff", + borderRadius: 24, + paddingHorizontal: 16, + paddingVertical: 10, + shadowColor: "#000", + shadowOpacity: 0.05, + shadowRadius: 10, + shadowOffset: { width: 0, height: 6 }, + elevation: 3, + }, + stepRow: { + flexDirection: "row", + alignItems: "center", + paddingVertical: 18, + }, + stepIndexWrap: { + width: 34, + height: 34, + borderRadius: 17, + backgroundColor: "#fff1f1", + alignItems: "center", + justifyContent: "center", + marginRight: 12, + }, + stepIndex: { + fontSize: 17, + color: RED, + fontWeight: "800", + }, + stepTextWrap: { + flex: 1, + paddingRight: 12, + }, + stepTitle: { + fontSize: 17, + color: DARK, + fontWeight: "700", + lineHeight: 24, + }, + stepDesc: { + fontSize: 14, + color: "#777777", + lineHeight: 20, + marginTop: 4, + }, + divider: { + height: 1, + backgroundColor: "#f0f0f0", + }, + loadingWrap: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + marginTop: 18, + }, + loadingText: { + marginLeft: 10, + fontSize: 14, + color: "#666666", + }, + resultCard: { + marginTop: 16, + backgroundColor: "#edf9f2", + borderRadius: 18, + paddingVertical: 14, + paddingHorizontal: 16, + flexDirection: "row", + alignItems: "center", + }, + resultText: { + marginLeft: 10, + color: "#156d45", + fontSize: 15, + fontWeight: "700", + }, + retryButton: { + marginTop: 18, + height: 46, + borderRadius: 14, + backgroundColor: RED, + alignItems: "center", + justifyContent: "center", + }, + retryButtonText: { + color: "#ffffff", + fontSize: 16, + fontWeight: "700", + }, +}); diff --git a/src/helper/ftmsIndoorBikeDataBus.ts b/src/helper/ftmsIndoorBikeDataBus.ts new file mode 100644 index 0000000..616f3c8 --- /dev/null +++ b/src/helper/ftmsIndoorBikeDataBus.ts @@ -0,0 +1,315 @@ +import { + Central, + ScannedPeripheral, + ConnectionStatus, +} from "@systemic-games/react-native-bluetooth-le"; + +const ftmsServiceUuid = "1826"; +const ftmsIndoorBikeDataUuid = "2ad2"; + +const fullUUID = (uuid: string) => + uuid.length === 4 + ? `0000${uuid}-0000-1000-8000-00805f9b34fb` + : uuid; + +export type FtmsIndoorBikeData = { + speedKph: number; + cadence: number; + power: number; + raw: Uint8Array; +}; + +type Listener = (data: FtmsIndoorBikeData) => void; + +type SubscriptionEntry = { + listeners: Set; + lastData: FtmsIndoorBikeData | null; + subscribePromise: Promise | null; + subscribed: boolean; + cadenceState: { + lastCadenceValue: number; + lastCadenceChangedTime: number; + }; + lastEmitTime: number; // ✅ throttle 用 +}; + +const entries = new Map(); +let connectionListenerInstalled = false; + +const getDeviceKey = (peripheral: ScannedPeripheral) => + String(peripheral.address || peripheral.systemId || peripheral.name || "unknown"); + +const resetEntrySubscriptionState = ( + key: string, + connectionStatus?: ConnectionStatus +) => { + const entry = entries.get(key); + if (!entry) return; + + entry.subscribed = false; + entry.subscribePromise = null; + entry.lastEmitTime = 0; + entry.cadenceState.lastCadenceValue = 0; + entry.cadenceState.lastCadenceChangedTime = 0; + + if (connectionStatus !== "connected" && connectionStatus !== "ready") { + entry.lastData = null; + } +}; + +const ensureConnectionListenerInstalled = () => { + if (connectionListenerInstalled) return; + + Central.addListener("peripheralConnectionStatus", (ev) => { + const key = getDeviceKey(ev.peripheral); + + if ( + ev.connectionStatus === "disconnected" || + ev.connectionStatus === "disconnecting" || + ev.connectionStatus === "connecting" + ) { + resetEntrySubscriptionState(key, ev.connectionStatus); + } + }); + + connectionListenerInstalled = true; +}; + +// ✅ 默认关闭调试(避免性能问题) +const DEBUG = false; + +const bytesToHex = (bytes: Uint8Array) => + Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0").toUpperCase()) + .join(""); + +const decodeBase64 = (value: string): Uint8Array | null => { + try { + const atobFn = (globalThis as { atob?: (data: string) => string }).atob; + + if (typeof atobFn === "function") { + const binary = atobFn(value); + const bytes = new Uint8Array(binary.length); + + for (let i = 0; i < binary.length; i += 1) { + bytes[i] = binary.charCodeAt(i); + } + + return bytes; + } + + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let buffer = 0; + let bits = 0; + const output: number[] = []; + + for (const char of value.replace(/=+$/, "")) { + const index = chars.indexOf(char); + if (index < 0) continue; + + buffer = (buffer << 6) | index; + bits += 6; + + if (bits >= 8) { + bits -= 8; + output.push((buffer >> bits) & 0xff); + } + } + + return new Uint8Array(output); + } catch (err) { + if (DEBUG) console.warn("[FTMS] decodeBase64 failed", err); + return null; + } +}; + +const toUint8Array = (raw: any): Uint8Array | null => { + try { + if (!raw) return null; + + if (raw instanceof Uint8Array) return raw; + if (raw instanceof ArrayBuffer) return new Uint8Array(raw); + if (typeof raw === "string") return decodeBase64(raw); + + if (Array.isArray(raw)) return new Uint8Array(raw); + + if (raw?.buffer instanceof ArrayBuffer) { + return new Uint8Array(raw.buffer, raw.byteOffset || 0, raw.byteLength); + } + + return null; + } catch (err) { + if (DEBUG) console.warn("[FTMS] toUint8Array failed", err); + return null; + } +}; + +const parseIndoorBikeData = ( + byteArray: Uint8Array, + cadenceState: SubscriptionEntry["cadenceState"] +): FtmsIndoorBikeData | null => { + if (byteArray.length < 8) return null; + + const currentTime = Date.now(); + const rawSpeed = byteArray[2] | (byteArray[3] << 8); + const rawCadence = byteArray[4] | (byteArray[5] << 8); + let rawPower = byteArray[6] | (byteArray[7] << 8); + + const speedKph = Number((rawSpeed / 100.0).toFixed(1)); + const cadenceValue = rawCadence / 2.0; + + if (cadenceValue !== cadenceState.lastCadenceValue) { + cadenceState.lastCadenceChangedTime = currentTime; + cadenceState.lastCadenceValue = cadenceValue; + } + + const cadence = + cadenceState.lastCadenceChangedTime > 0 && + currentTime - cadenceState.lastCadenceChangedTime > 3000 + ? 0 + : Math.round(cadenceValue); + + if (rawPower & 0x8000) { + rawPower -= 0x10000; + } + + const power = rawPower; + + return { + speedKph, + cadence, + power, + raw: byteArray, + }; +}; + +const ensureSubscribed = async ( + peripheral: ScannedPeripheral, + entry: SubscriptionEntry +) => { + if (entry.subscribed) return; + + if (entry.subscribePromise) { + await entry.subscribePromise; + return; + } + + entry.subscribePromise = Central.subscribeCharacteristic( + peripheral, + fullUUID(ftmsServiceUuid), + fullUUID(ftmsIndoorBikeDataUuid), + (notifyEv) => { + try { + const byteArray = toUint8Array(notifyEv.value); + if (!byteArray || byteArray.length === 0) return; + + if (DEBUG) { + const flags = + byteArray.length >= 2 + ? byteArray[0] | (byteArray[1] << 8) + : 0; + + console.log("[FTMS]", bytesToHex(byteArray)); + console.log( + "[FTMS] flags =", + "0x" + flags.toString(16).padStart(4, "0") + ); + } + + const parsed = parseIndoorBikeData( + byteArray, + entry.cadenceState + ); + if (!parsed) return; + + const now = Date.now(); + + // ✅ throttle: 限制为 10Hz(100ms 一次) + if (now - entry.lastEmitTime < 100) { + return; + } + + entry.lastEmitTime = now; + + if (DEBUG) { + console.log( + "[FTMS] parsed =>", + parsed.speedKph, + parsed.cadence, + parsed.power + ); + } + + entry.lastData = parsed; + entry.listeners.forEach((listener) => listener(parsed)); + } catch (err) { + if (DEBUG) console.warn("处理 2AD2 通知失败", err); + } + } + ) + .then(() => { + entry.subscribed = true; + if (DEBUG) console.log("[FTMS] subscribed"); + }) + .catch((err) => { + if (DEBUG) console.warn("[FTMS] subscribe failed", err); + throw err; + }) + .finally(() => { + entry.subscribePromise = null; + }); + + await entry.subscribePromise; +}; + +export const observeFtmsIndoorBikeData = async ( + peripheral: ScannedPeripheral, + listener: Listener +) => { + ensureConnectionListenerInstalled(); + + const key = getDeviceKey(peripheral); + let entry = entries.get(key); + + if (!entry) { + entry = { + listeners: new Set(), + lastData: null, + subscribePromise: null, + subscribed: false, + cadenceState: { + lastCadenceValue: 0, + lastCadenceChangedTime: 0, + }, + lastEmitTime: 0, // ✅ 初始化 + }; + entries.set(key, entry); + } + + if (entry.listeners.size === 0) { + resetEntrySubscriptionState(key, "disconnected"); + } + + entry.listeners.add(listener); + await ensureSubscribed(peripheral, entry); + + if (entry.lastData) { + listener(entry.lastData); + } + + return () => { + const currentEntry = entries.get(key); + if (!currentEntry) return; + currentEntry.listeners.delete(listener); + }; +}; + +export const debugFtmsIndoorBikeDataEntries = () => { + return Array.from(entries.entries()).map(([key, entry]) => ({ + key, + listeners: entry.listeners.size, + subscribed: entry.subscribed, + lastDataHex: entry.lastData ? bytesToHex(entry.lastData.raw) : null, + })); +}; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 58edb90..571c078 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -51,7 +51,7 @@ export const saveLanguage = async (language: string): Promise => { i18n .use(initReactI18next) .init({ - compatibilityJSON: 'v3', + compatibilityJSON: 'v4', resources: { zh: { translation: zh }, en: { translation: en }, @@ -68,4 +68,4 @@ getStoredLanguage().then((language) => { i18n.changeLanguage(language); }); -export default i18n; \ No newline at end of file +export default i18n; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 078bb35..e4ef065 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -9,7 +9,10 @@ "title": "POWERFUN Settings", "scan": "Scan Devices", "privacy": "Privacy Policy", - "version": "Version v0.0.1" + "version": "Version v0.0.1", + "powerMeter": "Power Meter", + "paddle": "Paddle", + "T5trainer": "T5 Trainer" }, "scan": { "title": "Scan Devices", @@ -19,6 +22,141 @@ "tipBluetooth": "(Please enable Bluetooth in settings)", "noName": "[No Name]", "rssiUnit": "dBm" + + }, + "t5Scan": { + "title": "Scan T5 Trainer", + "scanning": "Scanning...", + "noDevice": "No T5 trainers found", + "tipScanning": "(Make sure the trainer is powered on and awake)", + "tipBluetooth": "(Please enable Bluetooth in settings)" + }, + "paddleScan": { + "title": "Scan Paddle", + "scanning": "Scanning...", + "noDevice": "No paddle devices found", + "tipScanning": "(Make sure the paddle device is powered on and awake)", + "tipBluetooth": "(Please enable Bluetooth in settings)", + "noName": "[No Name]", + "rssiUnit": "dBm" + }, + "common": { + "notice": "Notice", + "unknown": "Unknown", + "unknownDevice": "Unknown Device", + "yes": "Yes", + "no": "No" + }, + "info2": { + "waitingAction": "Waiting", + "notMatched": "Not Matched", + "checkFailed": "Check Failed", + "unknownBoatType": "Unknown boat type", + "readAbnormal": "Unexpected response: {{hex}}", + "writingBoat": "Writing {{boatName}}...", + "writtenWaitRead": "Written {{boatName}}, reading back in 200ms...", + "requestingBoatRead": "Requesting boat type...", + "setSuccess": "Set success: {{boatName}}", + "notMatch": "Mismatch: expected {{expected}}, actual {{actual}}", + "readResult": "Read result: {{hex}}", + "actionFailed": "Action failed: {{message}}", + "failed": "Failed", + "waitFff3Timeout": "Timeout waiting for FFF3 response", + "currentBoat": "✅ Current boat type: {{boatName}}", + "readCurrentBoat": "Current boat type read: {{boatName}}", + "disconnectedNeedReconnect": "Device disconnected. Reconnect before upgrading.", + "defaultReadFailed": "Connected, but initial boat type read failed", + "connectOrReadFailed": "Connection or read failed", + "connectOrReadFailedAlert": "Bluetooth connection failed or device info read failed", + "cadence": "Cadence", + "cadenceUnit": "RPM (strokes/min)", + "boatSelect": "Boat Type", + "boatSwitching": "Switching boat type...", + "retry": "Please retry", + "boatKayak": "Kayak", + "boatRowing": "Rowing", + "boatRacing": "Racing", + "latestFirmware": "Latest Firmware", + "checking": "Checking..." + }, + "info3": { + "bikeTypeFollow": "Follow Software", + "bikeTypeRoad": "Road Bike", + "bikeTypeMtb26": "Mountain Bike 26\"", + "bikeTypeMtb275": "Mountain Bike 27.5\"", + "bikeTypeMtb29": "Mountain Bike 29\"", + "bikeTypeSmallWheel": "Small Wheel Bike", + "ergOn": "On", + "ergOff": "Off", + "readUsedHoursTimeout": "Timeout reading usage hours", + "readUsedMileageTimeout": "Timeout reading usage mileage", + "powerTrimRangeError": "Please enter a value between 50.00 and 200.00 with up to 2 decimals", + "deviceNotReady": "Device is not ready yet. Please try again later", + "invalidWeight": "Please enter a valid weight", + "waitFff3Timeout": "Timeout waiting for FFF3 response", + "readPowerTrimTimeout": "Timeout reading current power trim", + "waitWeightAckTimeout": "Timeout waiting for weight setting confirmation", + "readWeightTimeout": "Timeout reading current weight", + "readBikeTypeTimeout": "Timeout reading bike type", + "waitBikeTypeAckTimeout": "Timeout waiting for bike type confirmation", + "readErgTimeout": "Timeout reading ERG smoothing", + "waitErgAckTimeout": "Timeout waiting for ERG smoothing confirmation", + "connectReadFailed": "Device connection or read failed", + "readFailed": "Read failed", + "connecting": "Connecting", + "pendingTag": "Pending", + "settingTag": "Applying", + "currentWeightValue": "Current: {{value}} kg", + "currentPowerTrimValue": "Current: {{value}} %", + "currentTextValue": "Current: {{value}}", + "usedMileage": "Usage Mileage", + "speedKph": "Speed/km/h", + "weightSetting": "Weight Setting", + "powerTrim": "Power Trim", + "bikeType": "Bike Type", + "ergSmooth": "ERG Smoothing", + "settingWeight": "Setting weight...", + "weightSetDone": "Weight set successfully", + "weightSetFailed": "Weight setting failed", + "settingPowerTrim": "Setting power trim...", + "powerTrimSetDone": "Power trim set successfully", + "powerTrimSetFailed": "Power trim setting failed", + "settingBikeType": "Setting bike type...", + "bikeTypeSetDone": "Bike type set successfully", + "bikeTypeSetFailed": "Bike type setting failed", + "settingErgSmooth": "Setting ERG smoothing...", + "ergSmoothSetDone": "ERG smoothing set successfully", + "ergSmoothSetFailed": "ERG smoothing setting failed", + "confirmWeightChange": "Set weight to {{value}}kg?", + "confirmPowerTrimChange": "Set power trim to {{value}}%?", + "helpText": "Hold to view help" + }, + "spindown": { + "title": "Spindown", + "headerTitle": "Trainer Spindown", + "connecting": "Connecting", + "connected": "Connected", + "targetLabel": "Target Speed", + "targetHint": "Ride to reach the target speed first", + "currentSpeed": "Current Speed", + "statusReach36": "Ride to reach {{speed}} km/h", + "statusReached36": "Reached {{high}} km/h. Stop pedaling and wait until speed drops to {{low}} km/h", + "statusCalibrating": "Spindown in progress, please wait", + "statusCompleted": "Spindown completed", + "deviceNotReady": "Device not ready, please try again", + "connectFailed": "Failed to connect. Please go back and try again", + "timeout": "Timed out waiting for spindown time", + "failedRetry": "Spindown failed, please try again", + "step1Title": "Ride to {{speed}} km/h", + "step1Desc": "Automatically proceeds after reaching the target speed", + "step2Title": "Stop pedaling and coast", + "step2Desc": "Starts spindown automatically when speed drops to {{speed}} km/h", + "step3Title": "Spindown Completed", + "step3Loading": "Reading spindown time...", + "step3Pending": "Will appear here after completion", + "loading": "Connecting and preparing spindown...", + "result": "Spindown completed, time {{seconds}}s", + "retry": "Restart Spindown" }, "dfu": { "title": "Firmware Update", @@ -117,4 +255,4 @@ "calibrationSendError": "Failed to send calibration command", "error": "Error" } - } \ No newline at end of file + } diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index a71d9cd..a52b4e1 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -9,17 +9,154 @@ "title": "POWERFUN设置", "scan": "搜索设备", "privacy": "隐私协议", - "version": "版本号 v0.0.1" + "version": "版本号 v0.0.1", + "powerMeter": "功率计", + "paddle": "桨频器", + "T5trainer": "T5骑行台" }, "scan": { - "title": "搜索设备", - "scanning": "搜索中...", - "noDevice": "暂无设备", - "tipScanning": "(请确保设备有电且被唤醒)", - "tipBluetooth": "(请在设置中打开蓝牙)", - "noName": "[无名称]", - "rssiUnit": "dBm" + "title": "搜索设备", + "scanning": "搜索中...", + "noDevice": "暂无设备", + "tipScanning": "(请确保设备有电且被唤醒)", + "tipBluetooth": "(请在设置中打开蓝牙)", + "noName": "[无名称]", + "rssiUnit": "dBm" }, + "t5Scan": { + "title": "搜索T5骑行台", + "scanning": "搜索中...", + "noDevice": "暂无T5骑行台设备", + "tipScanning": "(请确保骑行台有电且被唤醒)", + "tipBluetooth": "(请在设置中打开蓝牙)" + }, + "paddleScan": { + "title": "搜索桨频器", + "scanning": "搜索中...", + "noDevice": "暂无桨频器设备", + "tipScanning": "(请确保桨频器设备有电且被唤醒)", + "tipBluetooth": "(请在设置中打开蓝牙)", + "noName": "[无名称]", + "rssiUnit": "dBm" + }, + "common": { + "notice": "提示", + "unknown": "未知", + "unknownDevice": "未知设备", + "yes": "是", + "no": "否" + }, + "info2": { + "waitingAction": "等待操作", + "notMatched": "未匹配", + "checkFailed": "检查失败", + "unknownBoatType": "读取到未知船型", + "readAbnormal": "读取返回异常:{{hex}}", + "writingBoat": "正在写入{{boatName}}...", + "writtenWaitRead": "已写入{{boatName}},200ms后读取确认...", + "requestingBoatRead": "正在请求读取船型...", + "setSuccess": "设置成功:{{boatName}}", + "notMatch": "不一致:期望 {{expected}},实际 {{actual}}", + "readResult": "读取返回:{{hex}}", + "actionFailed": "操作失败:{{message}}", + "failed": "失败", + "waitFff3Timeout": "等待 FFF3 返回超时", + "currentBoat": "✅ 当前船型:{{boatName}}", + "readCurrentBoat": "已读取当前船型:{{boatName}}", + "disconnectedNeedReconnect": "设备已断开,请重新连接", + "defaultReadFailed": "已连接,但默认读取船型失败", + "connectOrReadFailed": "连接或读取失败", + "connectOrReadFailedAlert": "蓝牙连接失败或设备信息读取失败", + "cadence": "桨频", + "cadenceUnit": "RPM(次/分钟)", + "boatSelect": "船型选择", + "boatSwitching": "船型切换中...", + "retry": "请重试", + "boatKayak": "皮艇", + "boatRowing": "划艇", + "boatRacing": "赛艇", + "latestFirmware": "最新固件", + "checking": "检查中..." + }, + "info3": { + "bikeTypeFollow": "跟随软件", + "bikeTypeRoad": "公路车", + "bikeTypeMtb26": "山地车26寸", + "bikeTypeMtb275": "山地车27.5寸", + "bikeTypeMtb29": "山地车29寸", + "bikeTypeSmallWheel": "小轮车", + "ergOn": "开启", + "ergOff": "关闭", + "readUsedMileageTimeout": "读取使用里程超时", + "powerTrimRangeError": "请输入50.00-200.00之间的数,最多保留两位小数", + "deviceNotReady": "设备尚未准备完成,请稍后再试", + "invalidWeight": "请输入正确的体重", + "waitFff3Timeout": "等待FFF3返回超时", + "readPowerTrimTimeout": "读取当前功率微调超时", + "waitWeightAckTimeout": "等待体重设定确认超时", + "readWeightTimeout": "读取当前体重超时", + "readBikeTypeTimeout": "读取当前车型超时", + "waitBikeTypeAckTimeout": "等待车型设定确认超时", + "readErgTimeout": "读取ERG功率平滑超时", + "waitErgAckTimeout": "等待ERG功率平滑确认超时", + "connectReadFailed": "设备连接或读取失败", + "readFailed": "读取失败", + "connecting": "连接中", + "pendingTag": "待保存", + "settingTag": "设置中", + "currentWeightValue": "当前:{{value}} kg", + "currentPowerTrimValue": "当前:{{value}} %", + "currentTextValue": "当前:{{value}}", + "usedMileage": "使用里程", + "speedKph": "速度/km/h", + "weightSetting": "体重设定", + "powerTrim": "功率微调", + "bikeType": "车型选择", + "ergSmooth": "ERG功率平滑", + "settingWeight": "正在设置体重…", + "weightSetDone": "体重设定完成", + "weightSetFailed": "体重设定失败", + "settingPowerTrim": "正在设置功率微调…", + "powerTrimSetDone": "功率微调设定完成", + "powerTrimSetFailed": "功率微调设定失败", + "settingBikeType": "车型设定中", + "bikeTypeSetDone": "车型设定完成", + "bikeTypeSetFailed": "车型设定失败", + "settingErgSmooth": "ERG功率平滑设定中", + "ergSmoothSetDone": "ERG功率平滑设定完成", + "ergSmoothSetFailed": "ERG功率平滑设定失败", + "confirmWeightChange": "是否将体重改为{{value}}kg", + "confirmPowerTrimChange": "是否将功率微调改为{{value}}%", + "helpText": "按住查看说明" + }, + "spindown": { + "title": "消旋", + "headerTitle": "骑行台消旋", + "connecting": "设备连接中", + "connected": "设备已连接", + "targetLabel": "目标速度", + "targetHint": "请先骑行加速到目标速度", + "currentSpeed": "当前速度", + "statusReach36": "请骑行加速到 {{speed}} km/h", + "statusReached36": "已达到 {{high}} km/h,请停止踩踏并等待速度下降到 {{low}} km/h", + "statusCalibrating": "正在消旋,请等待", + "statusCompleted": "消旋完成", + "deviceNotReady": "设备未准备好,请稍后重试", + "connectFailed": "设备连接失败,请返回重试", + "timeout": "等待消旋时间返回超时", + "failedRetry": "消旋失败,请重试", + "step1Title": "请骑行到 {{speed}} km/h", + "step1Desc": "达到目标速度后自动进入下一步", + "step2Title": "停止踩踏并等待减速", + "step2Desc": "速度下降到 {{speed}} km/h 后自动开始消旋", + "step3Title": "消旋完成", + "step3Loading": "正在读取消旋时间...", + "step3Pending": "完成前会显示在这里", + "loading": "正在连接设备并准备消旋...", + "result": "消旋完成,时间 {{seconds}}s", + "retry": "重新开始消旋" + }, + "dfu": { "title": "固件升级", "preparing": "准备中...", @@ -117,4 +254,4 @@ "calibrationSendError": "发送校准命令失败", "error": "错误" } - } \ No newline at end of file + } diff --git a/yarn.lock b/yarn.lock index 2d3e6e5..a2b7673 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,7 +16,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz" integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.25.2": +"@babel/core@^7.0.0", "@babel/core@^7.0.0 || ^8.0.0-0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.23.9", "@babel/core@^7.25.2", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0": version "7.28.4" resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz" integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== @@ -37,7 +37,7 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/eslint-parser@^7.25.1": +"@babel/eslint-parser@^7.12.0", "@babel/eslint-parser@^7.25.1": version "7.28.4" resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz" integrity sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA== @@ -966,7 +966,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.25.0", "@babel/runtime@^7.27.6", "@babel/runtime@^7.28.4": +"@babel/runtime@^7.25.0", "@babel/runtime@^7.28.4": version "7.28.4" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz" integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== @@ -980,7 +980,20 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/traverse@^7.25.3", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": version "7.28.4" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz" integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== @@ -1040,12 +1053,12 @@ "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" - resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz" + resolved "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== "@hapi/topo@^5.1.0": version "5.1.0" - resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz" + resolved "https://registry.npmmirror.com/@hapi/topo/-/topo-5.1.0.tgz" integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== dependencies: "@hapi/hoek" "^9.0.0" @@ -1277,6 +1290,17 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.npmmirror.com/@jest/types/-/types-26.6.2.tgz" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" @@ -1346,7 +1370,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -1361,63 +1385,50 @@ "@react-native-async-storage/async-storage@^2.2.0": version "2.2.0" - resolved "https://repo.huaweicloud.com/repository/npm/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz#a3aa565253e46286655560172f4e366e8969f5ad" + resolved "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz" integrity sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw== dependencies: merge-options "^3.0.4" -"@react-native-community/cli-clean@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.0.0.tgz" - integrity sha512-YmdNRcT+Dp8lC7CfxSDIfPMbVPEXVFzBH62VZNbYGxjyakqAvoQUFTYPgM2AyFusAr4wDFbDOsEv88gCDwR3ig== +"@react-native-community/cli-clean@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-clean/-/cli-clean-15.0.0.tgz" + integrity sha512-ndwVj77eYivHTRmwRBmiAhQq0nC012PDr9cqRQ5QUQl9xr9gXlyO26oWA9jJbXNydXf5DHsVqqDVvh97fERsbg== dependencies: - "@react-native-community/cli-tools" "20.0.0" + "@react-native-community/cli-tools" "15.0.0" chalk "^4.1.2" execa "^5.0.0" fast-glob "^3.3.2" -"@react-native-community/cli-config-android@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.0.0.tgz" - integrity sha512-asv60qYCnL1v0QFWcG9r1zckeFlKG+14GGNyPXY72Eea7RX5Cxdx8Pb6fIPKroWH1HEWjYH9KKHksMSnf9FMKw== +"@react-native-community/cli-config@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-config/-/cli-config-15.0.0.tgz" + integrity sha512-YwmQ9Q7JerwqYg0kMD+jwPer1x2ajPR7bjxkOzykfLK4AZxEZo+KgpkSTILMvdqW0WyaXwuYFsgtPa/YVaOn0A== dependencies: - "@react-native-community/cli-tools" "20.0.0" - chalk "^4.1.2" - fast-glob "^3.3.2" - fast-xml-parser "^4.4.1" - -"@react-native-community/cli-config-apple@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.0.0.tgz" - integrity sha512-PS1gNOdpeQ6w7dVu1zi++E+ix2D0ZkGC2SQP6Y/Qp002wG4se56esLXItYiiLrJkhH21P28fXdmYvTEkjSm9/Q== - dependencies: - "@react-native-community/cli-tools" "20.0.0" - chalk "^4.1.2" - execa "^5.0.0" - fast-glob "^3.3.2" - -"@react-native-community/cli-config@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.0.0.tgz" - integrity sha512-5Ky9ceYuDqG62VIIpbOmkg8Lybj2fUjf/5wK4UO107uRqejBgNgKsbGnIZgEhREcaSEOkujWrroJ9gweueLfBg== - dependencies: - "@react-native-community/cli-tools" "20.0.0" + "@react-native-community/cli-tools" "15.0.0" chalk "^4.1.2" cosmiconfig "^9.0.0" deepmerge "^4.3.0" fast-glob "^3.3.2" joi "^17.2.1" -"@react-native-community/cli-doctor@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.0.0.tgz" - integrity sha512-cPHspi59+Fy41FDVxt62ZWoicCZ1o34k8LAl64NVSY0lwPl+CEi78jipXJhtfkVqSTetloA8zexa/vSAcJy57Q== +"@react-native-community/cli-debugger-ui@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-15.0.0.tgz" + integrity sha512-S5A3QZv0ujP/TXZ+1lrlvRfetwuAvrSMJiBEcMh5pzObpr4Ura3naU6bh/ue+QFn9qJtNxoapC2c79B9Ngns/w== dependencies: - "@react-native-community/cli-config" "20.0.0" - "@react-native-community/cli-platform-android" "20.0.0" - "@react-native-community/cli-platform-apple" "20.0.0" - "@react-native-community/cli-platform-ios" "20.0.0" - "@react-native-community/cli-tools" "20.0.0" + serve-static "^1.13.1" + +"@react-native-community/cli-doctor@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-doctor/-/cli-doctor-15.0.0.tgz" + integrity sha512-UEavoARx1VRxZrNiiVWseP/6dBbP/qAJ9q7S4qf7iT6wstssxi+XCBwoONCQp5IIRJ8LAwKkxCksBuhoMDGzQg== + dependencies: + "@react-native-community/cli-config" "15.0.0" + "@react-native-community/cli-platform-android" "15.0.0" + "@react-native-community/cli-platform-apple" "15.0.0" + "@react-native-community/cli-platform-ios" "15.0.0" + "@react-native-community/cli-tools" "15.0.0" chalk "^4.1.2" command-exists "^1.2.8" deepmerge "^4.3.0" @@ -1426,88 +1437,92 @@ node-stream-zip "^1.9.1" ora "^5.4.1" semver "^7.5.2" + strip-ansi "^5.2.0" wcwidth "^1.0.1" yaml "^2.2.1" -"@react-native-community/cli-platform-android@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.0.0.tgz" - integrity sha512-th3ji1GRcV6ACelgC0wJtt9daDZ+63/52KTwL39xXGoqczFjml4qERK90/ppcXU0Ilgq55ANF8Pr+UotQ2AB/A== +"@react-native-community/cli-platform-android@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-platform-android/-/cli-platform-android-15.0.0.tgz" + integrity sha512-YQB48ulIdXqe/hEzPmVe5EU13AIQj/PNGZJSqHGoFs4wQYL4jR04iQ7wxIQSuw11TGZO3ne9rG4/rHt+3imE6Q== dependencies: - "@react-native-community/cli-config-android" "20.0.0" - "@react-native-community/cli-tools" "20.0.0" + "@react-native-community/cli-tools" "15.0.0" chalk "^4.1.2" execa "^5.0.0" + fast-glob "^3.3.2" + fast-xml-parser "^4.4.1" logkitty "^0.7.1" -"@react-native-community/cli-platform-apple@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.0.0.tgz" - integrity sha512-rZZCnAjUHN1XBgiWTAMwEKpbVTO4IHBSecdd1VxJFeTZ7WjmstqA6L/HXcnueBgxrzTCRqvkRIyEQXxC1OfhGw== +"@react-native-community/cli-platform-apple@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-platform-apple/-/cli-platform-apple-15.0.0.tgz" + integrity sha512-DUC4AL3AGNjUDkTrK71fBz2B/aloJm+NHc5deTfEicRvDkyHDM16RqkuFwcvrzaKOtnMDwuDNPM7/PSEp8tbVg== dependencies: - "@react-native-community/cli-config-apple" "20.0.0" - "@react-native-community/cli-tools" "20.0.0" + "@react-native-community/cli-tools" "15.0.0" chalk "^4.1.2" execa "^5.0.0" + fast-glob "^3.3.2" fast-xml-parser "^4.4.1" + ora "^5.4.1" -"@react-native-community/cli-platform-ios@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.0.0.tgz" - integrity sha512-Z35M+4gUJgtS4WqgpKU9/XYur70nmj3Q65c9USyTq6v/7YJ4VmBkmhC9BticPs6wuQ9Jcv0NyVCY0Wmh6kMMYw== +"@react-native-community/cli-platform-ios@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-15.0.0.tgz" + integrity sha512-2tP9R0tDIEA55ebNoVZFs0fQgz2nrnMy/epmsUrNC2p4+ZmPQEojqjB+OFaZV4Mh0svks+WoPqf9blk39kN7eg== dependencies: - "@react-native-community/cli-platform-apple" "20.0.0" + "@react-native-community/cli-platform-apple" "15.0.0" -"@react-native-community/cli-server-api@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.0.0.tgz" - integrity sha512-Ves21bXtjUK3tQbtqw/NdzpMW1vR2HvYCkUQ/MXKrJcPjgJnXQpSnTqHXz6ZdBlMbbwLJXOhSPiYzxb5/v4CDg== +"@react-native-community/cli-server-api@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-server-api/-/cli-server-api-15.0.0.tgz" + integrity sha512-ypq/5SghbuSaOFVaC+TGAlYCp5hTN0mZ6zBheBzD3OTWXhTu9UCBGCjubmBPLastXr0E6G0djTy4xZ5rwCrHWw== dependencies: - "@react-native-community/cli-tools" "20.0.0" - body-parser "^1.20.3" + "@react-native-community/cli-debugger-ui" "15.0.0" + "@react-native-community/cli-tools" "15.0.0" compression "^1.7.1" connect "^3.6.5" errorhandler "^1.5.1" nocache "^3.0.1" - open "^6.2.0" - pretty-format "^29.7.0" + pretty-format "^26.6.2" serve-static "^1.13.1" ws "^6.2.3" -"@react-native-community/cli-tools@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.0.0.tgz" - integrity sha512-akSZGxr1IajJ8n0YCwQoA3DI0HttJ0WB7M3nVpb0lOM+rJpsBN7WG5Ft+8ozb6HyIPX+O+lLeYazxn5VNG/Xhw== +"@react-native-community/cli-tools@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-tools/-/cli-tools-15.0.0.tgz" + integrity sha512-JZzHRJs+6F6or3tloXdbo6aSL2ifbvs7WKsEPjVFuXfaKNEzpQAqWAKMDr95VUEovuX942yD/QRLo6S2W5NTrw== dependencies: - "@vscode/sudo-prompt" "^9.0.0" appdirsjs "^1.2.4" chalk "^4.1.2" execa "^5.0.0" find-up "^5.0.0" - launch-editor "^2.9.1" mime "^2.4.1" + open "^6.2.0" ora "^5.4.1" prompts "^2.4.2" semver "^7.5.2" + shell-quote "^1.7.3" + sudo-prompt "^9.0.0" -"@react-native-community/cli-types@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.0.0.tgz" - integrity sha512-7J4hzGWOPTBV1d30Pf2NidV+bfCWpjfCOiGO3HUhz1fH4MvBM0FbbBmE9LE5NnMz7M8XSRSi68ZGYQXgLBB2Qw== +"@react-native-community/cli-types@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli-types/-/cli-types-15.0.0.tgz" + integrity sha512-sn+h6grsNxJFzKfOdzJX0HOIHbDnWiOo75+T4DBBdREfPTrq0Ao6NybxDWeircdMA6ovYrJLmjByls2MuCQMUA== dependencies: joi "^17.2.1" -"@react-native-community/cli@20.0.0": - version "20.0.0" - resolved "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.0.0.tgz" - integrity sha512-/cMnGl5V1rqnbElY1Fvga1vfw0d3bnqiJLx2+2oh7l9ulnXfVRWb5tU2kgBqiMxuDOKA+DQoifC9q/tvkj5K2w== +"@react-native-community/cli@*", "@react-native-community/cli@15.0.0": + version "15.0.0" + resolved "https://registry.npmmirror.com/@react-native-community/cli/-/cli-15.0.0.tgz" + integrity sha512-IzDIFCoWZsoOHLSKcd8OX9gAXnbH83vsyBIFaj/X6praDUA4VCnDf41mGGSOT/VEarGlarTa3tvRcqZ8aE5l/A== dependencies: - "@react-native-community/cli-clean" "20.0.0" - "@react-native-community/cli-config" "20.0.0" - "@react-native-community/cli-doctor" "20.0.0" - "@react-native-community/cli-server-api" "20.0.0" - "@react-native-community/cli-tools" "20.0.0" - "@react-native-community/cli-types" "20.0.0" + "@react-native-community/cli-clean" "15.0.0" + "@react-native-community/cli-config" "15.0.0" + "@react-native-community/cli-debugger-ui" "15.0.0" + "@react-native-community/cli-doctor" "15.0.0" + "@react-native-community/cli-server-api" "15.0.0" + "@react-native-community/cli-tools" "15.0.0" + "@react-native-community/cli-types" "15.0.0" chalk "^4.1.2" commander "^9.4.1" deepmerge "^4.3.0" @@ -1673,7 +1688,7 @@ hermes-parser "0.29.1" nullthrows "^1.1.1" -"@react-native/metro-config@0.81.4": +"@react-native/metro-config@*", "@react-native/metro-config@0.81.4": version "0.81.4" resolved "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.81.4.tgz" integrity sha512-aEXhRMsz6yN5X63Zk+cdKByQ0j3dsKv+ETRP9lLARdZ82fBOCMuK6IfmZMwK3A/3bI7gSvt2MFPn3QHy3WnByw== @@ -1756,19 +1771,19 @@ "@sideway/address@^4.1.5": version "4.1.5" - resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz" + resolved "https://registry.npmmirror.com/@sideway/address/-/address-4.1.5.tgz" integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== dependencies: "@hapi/hoek" "^9.0.0" "@sideway/formula@^3.0.1": version "3.0.1" - resolved "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz" + resolved "https://registry.npmmirror.com/@sideway/formula/-/formula-3.0.1.tgz" integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== "@sideway/pinpoint@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz" + resolved "https://registry.npmmirror.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== "@sinclair/typebox@^0.27.8": @@ -1888,9 +1903,9 @@ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/lodash@^4.17.21": - version "4.17.21" - resolved "https://repo.huaweicloud.com/repository/npm/@types/lodash/-/lodash-4.17.21.tgz#b806831543d696b14f8112db600ea9d3a1df6ea4" - integrity sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ== + version "4.17.24" + resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz" + integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ== "@types/node@*": version "24.5.2" @@ -1928,6 +1943,13 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== +"@types/yargs@^15.0.0": + version "15.0.20" + resolved "https://registry.npmmirror.com/@types/yargs/-/yargs-15.0.20.tgz" + integrity sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.33" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz" @@ -1935,7 +1957,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^7.1.1": +"@typescript-eslint/eslint-plugin@^5.0.0 || ^6.0.0 || ^7.0.0", "@typescript-eslint/eslint-plugin@^7.1.1": version "7.18.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz" integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== @@ -1950,7 +1972,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.1.1": +"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.1.1": version "7.18.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz" integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== @@ -2024,16 +2046,6 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.18.0": - version "7.18.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz" - integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.18.0" - "@typescript-eslint/types" "7.18.0" - "@typescript-eslint/typescript-estree" "7.18.0" - "@typescript-eslint/utils@^5.10.0": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz" @@ -2048,6 +2060,16 @@ eslint-scope "^5.1.1" semver "^7.3.7" +"@typescript-eslint/utils@7.18.0": + version "7.18.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz" + integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz" @@ -2069,11 +2091,6 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@vscode/sudo-prompt@^9.0.0": - version "9.3.1" - resolved "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.1.tgz" - integrity sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA== - abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" @@ -2081,7 +2098,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -accepts@^1.3.7, accepts@~1.3.7: +accepts@^1.3.7, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -2094,7 +2111,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.15.0, acorn@^8.9.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0, acorn@^8.9.0: version "8.15.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -2128,7 +2145,7 @@ ansi-escapes@^4.2.1: ansi-fragments@^0.2.1: version "0.2.1" - resolved "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz" + resolved "https://registry.npmmirror.com/ansi-fragments/-/ansi-fragments-0.2.1.tgz" integrity sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w== dependencies: colorette "^1.0.7" @@ -2137,7 +2154,7 @@ ansi-fragments@^0.2.1: ansi-regex@^4.1.0: version "4.1.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.0, ansi-regex@^5.0.1: @@ -2147,7 +2164,7 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: ansi-styles@^3.2.0: version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" @@ -2174,7 +2191,7 @@ anymatch@^3.0.3: appdirsjs@^1.2.4: version "1.2.7" - resolved "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz" + resolved "https://registry.npmmirror.com/appdirsjs/-/appdirsjs-1.2.7.tgz" integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw== argparse@^1.0.7: @@ -2279,7 +2296,7 @@ asap@~2.0.6: astral-regex@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz" + resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-1.0.0.tgz" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-function@^1.0.0: @@ -2412,7 +2429,7 @@ base-64@^0.1.0: base-64@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz" + resolved "https://registry.npmmirror.com/base-64/-/base-64-1.0.0.tgz" integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg== base64-js@^1.3.1, base64-js@^1.5.1: @@ -2427,31 +2444,13 @@ baseline-browser-mapping@^2.8.3: bl@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" + resolved "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" inherits "^2.0.4" readable-stream "^3.4.0" -body-parser@^1.20.3: - version "1.20.3" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - brace-expansion@^1.1.7: version "1.1.12" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" @@ -2474,7 +2473,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.25.3: +browserslist@^4.24.0, browserslist@^4.25.3, "browserslist@>= 4.21.0": version "4.26.2" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz" integrity sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== @@ -2499,7 +2498,7 @@ buffer-from@^1.0.0: buffer@^5.5.0: version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -2507,7 +2506,7 @@ buffer@^5.5.0: bytes@3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + resolved "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: @@ -2608,19 +2607,19 @@ cjs-module-lexer@^1.0.0: cli-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" + resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz" integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: restore-cursor "^3.1.0" cli-spinners@^2.5.0: version "2.9.2" - resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" + resolved "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== cliui@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" + resolved "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz" integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: string-width "^4.2.0" @@ -2647,7 +2646,7 @@ cliui@^8.0.1: clone@^1.0.2: version "1.0.4" - resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" + resolved "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== co@^4.6.0: @@ -2662,7 +2661,7 @@ collect-v8-coverage@^1.0.0: color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" @@ -2674,16 +2673,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-string@^1.9.0: version "1.9.1" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" @@ -2702,12 +2701,12 @@ color@^4.2.3: colorette@^1.0.7: version "1.4.0" - resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz" + resolved "https://registry.npmmirror.com/colorette/-/colorette-1.4.0.tgz" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== command-exists@^1.2.8: version "1.2.9" - resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz" + resolved "https://registry.npmmirror.com/command-exists/-/command-exists-1.2.9.tgz" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== commander@^12.0.0: @@ -2727,14 +2726,14 @@ commander@^9.4.1: compressible@~2.0.18: version "2.0.18" - resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" + resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: mime-db ">= 1.43.0 < 2" compression@^1.7.1: version "1.8.1" - resolved "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz" + resolved "https://registry.npmmirror.com/compression/-/compression-1.8.1.tgz" integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: bytes "3.1.2" @@ -2760,11 +2759,6 @@ connect@^3.6.5: parseurl "~1.3.3" utils-merge "1.0.1" -content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" @@ -2778,9 +2772,9 @@ core-js-compat@^3.43.0: browserslist "^4.25.3" cosmiconfig@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + version "9.0.1" + resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-9.0.1.tgz" + integrity sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ== dependencies: env-paths "^2.2.1" import-fresh "^3.3.0" @@ -2842,27 +2836,34 @@ data-view-byte-offset@^1.0.1: is-data-view "^1.0.1" dayjs@^1.8.15: - version "1.11.18" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz" - integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA== + version "1.11.20" + resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz" + integrity sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ== -debug@2.6.9, debug@^2.6.9: +debug@^2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.1: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.1, debug@4: version "4.4.3" resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decode-uri-component@^0.2.2: @@ -2887,7 +2888,7 @@ deepmerge@^4.2.2, deepmerge@^4.3.0: defaults@^1.0.3: version "1.0.4" - resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz" + resolved "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz" integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== dependencies: clone "^1.0.2" @@ -2992,13 +2993,13 @@ encodeurl@~2.0.0: env-paths@^2.2.1: version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" + resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envinfo@^7.13.0: - version "7.14.0" - resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz" - integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg== + version "7.21.0" + resolved "https://registry.npmmirror.com/envinfo/-/envinfo-7.21.0.tgz" + integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== error-ex@^1.3.1: version "1.3.4" @@ -3015,11 +3016,11 @@ error-stack-parser@^2.0.6: stackframe "^1.3.4" errorhandler@^1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz" - integrity sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A== + version "1.5.2" + resolved "https://registry.npmmirror.com/errorhandler/-/errorhandler-1.5.2.tgz" + integrity sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" escape-html "~1.0.3" es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: @@ -3241,7 +3242,7 @@ eslint-plugin-react@^7.30.1: string.prototype.matchall "^4.0.12" string.prototype.repeat "^1.0.0" -eslint-scope@5.1.1, eslint-scope@^5.1.1: +eslint-scope@^5.1.1, eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -3262,12 +3263,22 @@ eslint-visitor-keys@^2.1.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.3.0: version "3.4.3" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.19.0: +eslint-visitor-keys@^3.4.1: + version "3.4.3" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +"eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", "eslint@^7.5.0 || ^8.0.0 || ^9.0.0", eslint@^8.1.0, eslint@^8.19.0, eslint@^8.56.0, eslint@>=4.19.1, eslint@>=7.0.0, eslint@>=8: version "8.57.1" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -3432,11 +3443,11 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-xml-parser@^4.4.1: - version "4.5.3" - resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz" - integrity sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig== + version "4.5.5" + resolved "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.5.5.tgz" + integrity sha512-cK9c5I/DwIOI7/Q7AlGN3DuTdwN61gwSfL8rvuVPK+0mcCNHHGxRrpiFtaZZRfRMJL3Gl8B2AFlBG6qXf03w9A== dependencies: - strnum "^1.1.1" + strnum "^1.0.5" fastq@^1.6.0: version "1.19.1" @@ -3484,7 +3495,7 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3492,6 +3503,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" @@ -3767,7 +3786,7 @@ html-escaper@^2.0.0: html-parse-stringify@^3.0.1: version "3.0.1" - resolved "https://repo.huaweicloud.com/repository/npm/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + resolved "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz" integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== dependencies: void-elements "3.1.0" @@ -3796,23 +3815,16 @@ human-signals@^2.1.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -i18next@^25.7.3: - version "25.7.3" - resolved "https://repo.huaweicloud.com/repository/npm/i18next/-/i18next-25.7.3.tgz#88bf15b414012a53ca87b9d408108aa81cb627cc" - integrity sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA== +i18next@^25.7.3, "i18next@>= 25.6.2": + version "25.8.14" + resolved "https://registry.npmmirror.com/i18next/-/i18next-25.8.14.tgz" + integrity sha512-paMUYkfWJMsWPeE/Hejcw+XLhHrQPehem+4wMo+uELnvIwvCG019L9sAIljwjCmEMtFQQO3YeitJY8Kctei3iA== dependencies: "@babel/runtime" "^7.28.4" -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - ieee754@^1.1.13: version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^5.0.5, ignore@^5.2.0, ignore@^5.3.1: @@ -3856,7 +3868,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3970,7 +3982,7 @@ is-finalizationregistry@^1.1.0: is-fullwidth-code-point@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: @@ -4002,7 +4014,7 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: is-interactive@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" + resolved "https://registry.npmmirror.com/is-interactive/-/is-interactive-1.0.0.tgz" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== is-map@^2.0.3: @@ -4035,7 +4047,7 @@ is-path-inside@^3.0.3: is-plain-obj@^2.1.0: version "2.1.0" - resolved "https://repo.huaweicloud.com/repository/npm/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-regex@^1.2.1: @@ -4091,7 +4103,7 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" + resolved "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-weakmap@^2.0.2: @@ -4116,10 +4128,17 @@ is-weakset@^2.0.3: is-wsl@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" + resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-1.1.0.tgz" integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -4405,7 +4424,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@^29.7.0: +jest-resolve@*, jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -4549,7 +4568,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.6.3: +jest@*, jest@^29.6.3: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -4561,7 +4580,7 @@ jest@^29.6.3: joi@^17.2.1: version "17.13.3" - resolved "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz" + resolved "https://registry.npmmirror.com/joi/-/joi-17.13.3.tgz" integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== dependencies: "@hapi/hoek" "^9.3.0" @@ -4659,14 +4678,6 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -launch-editor@^2.9.1: - version "2.11.1" - resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz" - integrity sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg== - dependencies: - picocolors "^1.1.1" - shell-quote "^1.8.3" - leven@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" @@ -4729,7 +4740,7 @@ lodash@^4.17.21: log-symbols@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" + resolved "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" @@ -4737,7 +4748,7 @@ log-symbols@^4.1.0: logkitty@^0.7.1: version "0.7.1" - resolved "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz" + resolved "https://registry.npmmirror.com/logkitty/-/logkitty-0.7.1.tgz" integrity sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ== dependencies: ansi-fragments "^0.2.1" @@ -4782,11 +4793,6 @@ math-intrinsics@^1.1.0: resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz" @@ -4794,7 +4800,7 @@ memoize-one@^5.0.0: merge-options@^3.0.4: version "3.0.4" - resolved "https://repo.huaweicloud.com/repository/npm/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + resolved "https://registry.npmmirror.com/merge-options/-/merge-options-3.0.4.tgz" integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== dependencies: is-plain-obj "^2.1.0" @@ -4836,7 +4842,7 @@ metro-cache@0.83.2: https-proxy-agent "^7.0.5" metro-core "0.83.2" -metro-config@0.83.2, metro-config@^0.83.1: +metro-config@^0.83.1, metro-config@0.83.2: version "0.83.2" resolved "https://registry.npmjs.org/metro-config/-/metro-config-0.83.2.tgz" integrity sha512-1FjCcdBe3e3D08gSSiU9u3Vtxd7alGH3x/DNFqWDFf5NouX4kLgbVloDDClr1UrLz62c0fHh2Vfr9ecmrOZp+g== @@ -4850,7 +4856,7 @@ metro-config@0.83.2, metro-config@^0.83.1: metro-runtime "0.83.2" yaml "^2.6.1" -metro-core@0.83.2, metro-core@^0.83.1: +metro-core@^0.83.1, metro-core@0.83.2: version "0.83.2" resolved "https://registry.npmjs.org/metro-core/-/metro-core-0.83.2.tgz" integrity sha512-8DRb0O82Br0IW77cNgKMLYWUkx48lWxUkvNUxVISyMkcNwE/9ywf1MYQUE88HaKwSrqne6kFgCSA/UWZoUT0Iw== @@ -4889,7 +4895,7 @@ metro-resolver@0.83.2: dependencies: flow-enums-runtime "^0.0.6" -metro-runtime@0.83.2, metro-runtime@^0.83.1: +metro-runtime@^0.83.1, metro-runtime@0.83.2: version "0.83.2" resolved "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.2.tgz" integrity sha512-nnsPtgRvFbNKwemqs0FuyFDzXLl+ezuFsUXDbX8o0SXOfsOPijqiQrf3kuafO1Zx1aUWf4NOrKJMAQP5EEHg9A== @@ -4897,7 +4903,7 @@ metro-runtime@0.83.2, metro-runtime@^0.83.1: "@babel/runtime" "^7.25.0" flow-enums-runtime "^0.0.6" -metro-source-map@0.83.2, metro-source-map@^0.83.1: +metro-source-map@^0.83.1, metro-source-map@0.83.2: version "0.83.2" resolved "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.2.tgz" integrity sha512-5FL/6BSQvshIKjXOennt9upFngq2lFvDakZn5LfauIVq8+L4sxXewIlSTcxAtzbtjAIaXeOSVMtCJ5DdfCt9AA== @@ -4956,7 +4962,7 @@ metro-transform-worker@0.83.2: metro-transform-plugins "0.83.2" nullthrows "^1.1.1" -metro@0.83.2, metro@^0.83.1: +metro@^0.83.1, metro@0.83.2: version "0.83.2" resolved "https://registry.npmjs.org/metro/-/metro-0.83.2.tgz" integrity sha512-HQgs9H1FyVbRptNSMy/ImchTTE5vS2MSqLoOo7hbDoBq6hPPZokwJvBMwrYSxdjQZmLXz2JFZtdvS+ZfgTc9yw== @@ -5010,39 +5016,60 @@ micromatch@^4.0.4, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" +"mime-db@>= 1.43.0 < 2": + version "1.54.0" + resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -"mime-db@>= 1.43.0 < 2": - version "1.54.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" +mime@^2.4.1: + version "2.6.0" + resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mime@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.1: - version "2.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.5: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -5061,16 +5088,16 @@ mkdirp@^1.0.4: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +ms@^2.1.3, ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.3, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - nanoid@^3.3.11: version "3.3.11" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" @@ -5081,19 +5108,19 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + negotiator@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -negotiator@~0.6.4: - version "0.6.4" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz" - integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== - nocache@^3.0.1: version "3.0.4" - resolved "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz" + resolved "https://registry.npmmirror.com/nocache/-/nocache-3.0.4.tgz" integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== node-int64@^0.4.0: @@ -5108,7 +5135,7 @@ node-releases@^2.0.21: node-stream-zip@^1.9.1: version "1.15.0" - resolved "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz" + resolved "https://registry.npmmirror.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz" integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== normalize-path@^3.0.0: @@ -5192,13 +5219,6 @@ object.values@^1.1.6, object.values@^1.2.1: define-properties "^1.2.1" es-object-atoms "^1.0.0" -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" @@ -5206,9 +5226,16 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + on-headers@~1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz" + resolved "https://registry.npmmirror.com/on-headers/-/on-headers-1.1.0.tgz" integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== once@^1.3.0: @@ -5227,7 +5254,7 @@ onetime@^5.1.0, onetime@^5.1.2: open@^6.2.0: version "6.4.0" - resolved "https://registry.npmjs.org/open/-/open-6.4.0.tgz" + resolved "https://registry.npmmirror.com/open/-/open-6.4.0.tgz" integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== dependencies: is-wsl "^1.1.0" @@ -5254,7 +5281,7 @@ optionator@^0.9.3: ora@^5.4.1: version "5.4.1" - resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" + resolved "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: bl "^4.1.0" @@ -5388,11 +5415,21 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@2.8.8: +prettier@>=2, prettier@2.8.8: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-26.6.2.tgz" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" @@ -5436,13 +5473,6 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -qs@6.13.0: - version "6.13.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - query-string@^7.1.3: version "7.1.3" resolved "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz" @@ -5470,16 +5500,6 @@ range-parser@~1.2.1: resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - react-devtools-core@^6.1.5: version "6.1.5" resolved "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz" @@ -5494,11 +5514,11 @@ react-freeze@^1.0.0: integrity sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA== react-i18next@^16.5.0: - version "16.5.0" - resolved "https://repo.huaweicloud.com/repository/npm/react-i18next/-/react-i18next-16.5.0.tgz#107e4323742344a2f8792feb905cea551da6fd2c" - integrity sha512-IMpPTyCTKxEj8klCrLKUTIUa8uYTd851+jcu2fJuUB9Agkk9Qq8asw4omyeHVnOXHrLgQJGTm5zTvn8HpaPiqw== + version "16.5.6" + resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-16.5.6.tgz" + integrity sha512-Ua7V2/efA88ido7KyK51fb8Ki8M/sRfW8LR/rZ/9ZKr2luhuTI7kwYZN5agT1rWG7aYm5G0RYE/6JR8KJoCMDw== dependencies: - "@babel/runtime" "^7.27.6" + "@babel/runtime" "^7.28.4" html-parse-stringify "^3.0.1" use-sync-external-store "^1.6.0" @@ -5507,6 +5527,11 @@ react-is@^16.13.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.0.0: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" @@ -5518,9 +5543,9 @@ react-is@^19.1.0: integrity sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA== react-native-device-info@^15.0.1: - version "15.0.1" - resolved "https://repo.huaweicloud.com/repository/npm/react-native-device-info/-/react-native-device-info-15.0.1.tgz#8a2716861bb8e491e0627f326bcc007c77d2e8f6" - integrity sha512-U5waZRXtT3l1SgZpZMlIvMKPTkFZPH8W7Ks6GrJhdH723aUIPxjVer7cRSij1mvQdOAAYFJV/9BDzlC8apG89A== + version "15.0.2" + resolved "https://registry.npmmirror.com/react-native-device-info/-/react-native-device-info-15.0.2.tgz" + integrity sha512-dd71eXG2l3Cwp66IvKNadMTB8fhU3PEjyVddI97sYan+D4bgIAUmgGDhbSOFvHcGavksb2U17kiQYaDiK2WK2g== react-native-fs@^2.20.0: version "2.20.0" @@ -5535,17 +5560,17 @@ react-native-is-edge-to-edge@^1.2.1: resolved "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz" integrity sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q== -react-native-safe-area-context@^5.6.1: +react-native-safe-area-context@^5.6.1, "react-native-safe-area-context@>= 4.0.0": version "5.6.1" resolved "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz" integrity sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA== react-native-safearea-height@^1.0.8: version "1.0.8" - resolved "https://registry.npmmirror.com/react-native-safearea-height/-/react-native-safearea-height-1.0.8.tgz#efa2cecc2dfb2368288c435603ade3ee9c695479" + resolved "https://registry.npmmirror.com/react-native-safearea-height/-/react-native-safearea-height-1.0.8.tgz" integrity sha512-nAACsA6HwbMI5zUKHUy4IOvqrohA2AFYB31Yhv6JOXbok6S7VOjOWJZNJx4dllECREon254Y2ZlqeVGUb2eqgQ== -react-native-screens@^4.16.0: +react-native-screens@^4.16.0, "react-native-screens@>= 4.0.0": version "4.16.0" resolved "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz" integrity sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q== @@ -5556,7 +5581,7 @@ react-native-screens@^4.16.0: react-native-toast-message@^2.3.3: version "2.3.3" - resolved "https://repo.huaweicloud.com/repository/npm/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz#e301508d386a9902ff6b4559ecc6674f8cfdf97a" + resolved "https://registry.npmmirror.com/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz" integrity sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ== react-native-vector-icons@^10.3.0: @@ -5567,7 +5592,7 @@ react-native-vector-icons@^10.3.0: prop-types "^15.7.2" yargs "^16.1.1" -react-native@0.81.4: +react-native@*, "react-native@^0.0.0-0 || >=0.65 <1.0", react-native@0.81.4: version "0.81.4" resolved "https://registry.npmjs.org/react-native/-/react-native-0.81.4.tgz" integrity sha512-bt5bz3A/+Cv46KcjV0VQa+fo7MKxs17RCcpzjftINlen4ZDUl0I6Ut+brQ2FToa5oD0IB0xvQHfmsg2EDqsZdQ== @@ -5620,14 +5645,14 @@ react-test-renderer@19.1.0: react-is "^19.1.0" scheduler "^0.26.0" -react@19.1.0: +react@*, "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react@^19.1.0, "react@>= 16.8.0", "react@>= 18.2.0", react@>=16.8, react@>=17.0.0, react@19.1.0: version "19.1.0" resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz" integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== readable-stream@^3.4.0: version "3.6.2" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" @@ -5713,7 +5738,7 @@ require-directory@^2.1.1: require-main-filename@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" + resolved "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== resolve-cwd@^3.0.0: @@ -5758,7 +5783,7 @@ resolve@^2.0.0-next.5: restore-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" + resolved "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz" integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: onetime "^5.1.0" @@ -5794,9 +5819,9 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@~5.2.0: +safe-buffer@~5.2.0, safe-buffer@5.2.1: version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-push-apply@^1.0.0: @@ -5816,12 +5841,7 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -scheduler@0.26.0, scheduler@^0.26.0: +scheduler@^0.26.0, scheduler@0.26.0: version "0.26.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz" integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== @@ -5831,7 +5851,32 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.1.3, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: +semver@^7.1.3: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.3.7: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.5.2: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.5.3: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.5.4: + version "7.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +semver@^7.6.0: version "7.7.2" resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== @@ -5872,7 +5917,7 @@ serve-static@^1.13.1, serve-static@^1.16.2: set-blocking@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== set-function-length@^1.2.2: @@ -5923,7 +5968,7 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1, shell-quote@^1.8.3: +shell-quote@^1.6.1, shell-quote@^1.7.3: version "1.8.3" resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== @@ -5957,7 +6002,7 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.6, side-channel@^1.1.0: +side-channel@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== @@ -5992,21 +6037,13 @@ slash@^3.0.0: slice-ansi@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz" + resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-2.1.0.tgz" integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" @@ -6015,6 +6052,14 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map@^0.5.6: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" @@ -6054,16 +6099,16 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - statuses@~1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + stop-iteration-iterator@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz" @@ -6077,6 +6122,13 @@ strict-uri-encode@^2.0.0: resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -6158,16 +6210,16 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - strip-ansi@^5.0.0: version "5.2.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" @@ -6194,11 +6246,16 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strnum@^1.1.1: +strnum@^1.0.5: version "1.1.2" - resolved "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz" + resolved "https://registry.npmmirror.com/strnum/-/strnum-1.1.2.tgz" integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== +sudo-prompt@^9.0.0: + version "9.2.1" + resolved "https://registry.npmmirror.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz" + integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" @@ -6308,14 +6365,6 @@ type-fest@^0.7.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typed-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz" @@ -6361,7 +6410,7 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typescript@^5.8.3: +typescript@^5, typescript@^5.8.3, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=4.2.0, typescript@>=4.9.5: version "5.9.2" resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== @@ -6409,7 +6458,7 @@ universalify@^0.1.0: resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== @@ -6434,14 +6483,9 @@ use-latest-callback@^0.2.4: resolved "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.4.tgz" integrity sha512-LS2s2n1usUUnDq4oVh1ca6JFX9uSqUncTfAm44WMg0v6TxL7POUTk1B044NH8TeLkFbNajIsgDHcgNpNzZucdg== -use-sync-external-store@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz" - integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== - -use-sync-external-store@^1.6.0: +use-sync-external-store@^1.5.0, use-sync-external-store@^1.6.0: version "1.6.0" - resolved "https://repo.huaweicloud.com/repository/npm/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz" integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== utf8@^3.0.0: @@ -6451,7 +6495,7 @@ utf8@^3.0.0: util-deprecate@^1.0.1: version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== utils-merge@1.0.1: @@ -6470,7 +6514,7 @@ v8-to-istanbul@^9.0.1: vary@~1.1.2: version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== vlq@^1.0.0: @@ -6480,7 +6524,7 @@ vlq@^1.0.0: void-elements@3.1.0: version "3.1.0" - resolved "https://repo.huaweicloud.com/repository/npm/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + resolved "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== walker@^1.0.7, walker@^1.0.8: @@ -6497,7 +6541,7 @@ warn-once@^0.1.0, warn-once@^0.1.1: wcwidth@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" + resolved "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz" integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== dependencies: defaults "^1.0.3" @@ -6549,7 +6593,7 @@ which-collection@^1.0.2: which-module@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" + resolved "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== which-typed-array@^1.1.16, which-typed-array@^1.1.19: @@ -6579,7 +6623,7 @@ word-wrap@^1.2.5: wrap-ansi@^6.2.0: version "6.2.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" @@ -6615,14 +6659,19 @@ ws@^6.2.3: dependencies: async-limiter "~1.0.0" -ws@^7, ws@^7.5.10: +ws@^7: + version "7.5.10" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + +ws@^7.5.10: version "7.5.10" resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== y18n@^4.0.0: version "4.0.3" - resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" + resolved "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== y18n@^5.0.5: @@ -6642,7 +6691,7 @@ yaml@^2.2.1, yaml@^2.6.1: yargs-parser@^18.1.2: version "18.1.3" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" + resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" @@ -6660,7 +6709,7 @@ yargs-parser@^21.1.1: yargs@^15.1.0: version "15.4.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" + resolved "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: cliui "^6.0.0"