diff --git a/.gitignore b/.gitignore index ceed365cc..acb28a32b 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,9 @@ migrations.json .DS_Store Thumbs.db +# Remote miniapps (downloaded at dev/build time) +miniapps/rwa-hub/ + # Playwright e2e/test-results/ e2e/report/ diff --git a/docs/white-book/08-Security-Ref/05-CryptoBox-Authorization.md b/docs/white-book/08-Security-Ref/05-CryptoBox-Authorization.md new file mode 100644 index 000000000..c535fe4aa --- /dev/null +++ b/docs/white-book/08-Security-Ref/05-CryptoBox-Authorization.md @@ -0,0 +1,242 @@ +# Crypto Box 授权系统 + +> 源码: [`src/services/crypto-box/`](https://github.com/BioforestChain/KeyApp/blob/main/src/services/crypto-box/) + +--- + +## 概述 + +Crypto Box 是一个"加密黑盒"系统,允许 Miniapp 执行加密操作(非对称加密、签名)而**不暴露私钥**。 + +``` +┌─────────────────┐ ┌─────────────────┐ +│ Miniapp │ bio_requestToken │ KeyApp │ +│ (RWA Hub) │ ◄────────────────► │ (Crypto Box) │ +│ │ bio_cryptoExecute │ │ +│ 无法访问私钥 │ │ 私钥安全存储 │ +└─────────────────┘ └─────────────────┘ +``` + +--- + +## 安全设计 + +### Token 授权流程 + +``` +1. Miniapp 请求 Token + │ + ▼ +2. 用户输入手势密码授权 + │ + ▼ +3. 派生 sessionSecret = SHA256(walletId + patternKey + miniappId + tokenId) + │ + ▼ +4. 加密 Payload(含 patternKey)存入 IndexedDB + │ + ▼ +5. 返回 tokenId + sessionSecret 给 Miniapp + │ + ▼ +6. Miniapp 使用 tokenId + sessionSecret 执行加密操作 + │ + ▼ +7. KeyApp 解密 Payload,获取 patternKey,执行操作 +``` + +### 密钥安全 + +| 安全约束 | 实现 | +|----------|------| +| **MUST** | patternKey 不在内存中长期保留 | +| **MUST** | patternKey 使用 sessionSecret 加密存储 | +| **MUST** | sessionSecret 派生需要 patternKey 参与 | +| **MUST** | Token 验证从加密 Payload 读取(不信任明文副本)| + +### sessionSecret 派生 + +```typescript +sessionSecret = SHA256(walletId + ":" + patternKey + ":" + miniappId + ":" + tokenId) +``` + +- 包含 `patternKey`:确保只有授权时的用户才能派生相同的 secret +- 包含 `tokenId`:每个 Token 的 sessionSecret 唯一 +- 包含 `miniappId`:防止跨 app 使用 + +--- + +## API 参考 + +### bio_requestCryptoToken + +请求加密操作授权,用户需输入手势密码。 + +**请求参数:** + +```typescript +interface RequestCryptoTokenParams { + actions: ('asymmetricEncrypt' | 'sign')[] + duration: '5min' | '15min' | '1hour' | '1day' + address: string + chainId?: string +} +``` + +**响应:** + +```typescript +interface RequestCryptoTokenResponse { + tokenId: string + sessionSecret: string // 用于后续执行 + expiresAt: number + grantedActions: CryptoAction[] +} +``` + +### bio_cryptoExecute + +使用 Token 执行加密操作。 + +**请求参数:** + +```typescript +interface CryptoExecuteParams { + tokenId: string + sessionSecret: string + action: 'asymmetricEncrypt' | 'sign' + params: AsymmetricEncryptParams | SignParams +} +``` + +**响应:** + +```typescript +interface CryptoExecuteResponse { + result: string // hex 编码结果 + publicKey: string // hex 编码公钥 +} +``` + +--- + +## Token 存储 + +### IndexedDB Schema + +```typescript +interface StoredToken { + tokenId: string + miniappId: string // 明文副本(便于查询) + walletId: string // 明文副本 + address: string // 明文副本 + actions: CryptoAction[] // 明文副本 + expiresAt: number // 明文副本 + createdAt: number + encryptedPayload: string // 加密的真实数据 +} +``` + +### 加密 Payload 内容 + +```typescript +interface TokenPayload { + patternKey: string // 手势密码(加密存储) + miniappId: string + walletId: string + address: string + actions: CryptoAction[] + expiresAt: number +} +``` + +验证时**始终从解密的 Payload 读取**,明文副本仅用于查询展示。 + +--- + +## 非对称加密实现 + +### Ed25519 → X25519 转换 + +BFMetaSignUtil 使用 X25519 ECDH,而 KeyApp 使用 Ed25519 密钥对: + +```typescript +import ed2curve from 'ed2curve' + +// 转换公钥 +const curvePublicKey = ed2curve.convertPublicKey(ed25519PublicKey) + +// 转换私钥 +const curveSecretKey = ed2curve.convertSecretKey(ed25519SecretKey) + +// X25519 box 加密 +const encrypted = nacl.box(message, nonce, curveRecipientPK, curveSecretKey) +``` + +### 固定 Nonce + +与 BFMetaSignUtil 兼容,使用全零 nonce: + +```typescript +const nonce = new Uint8Array(24) // 24 字节全零 +``` + +> ⚠️ 固定 nonce 在密码学上不推荐,但为兼容现有系统必须使用。 + +--- + +## 错误代码 + +| 代码 | 含义 | +|------|------| +| 4100 | Token 未找到 | +| 4101 | Miniapp ID 不匹配 | +| 4102 | Token 已过期 | +| 4103 | 操作未授权 | +| 4104 | Session Secret 无效 | +| 4001 | 用户拒绝授权 | + +--- + +## 使用示例 + +### RWA Hub 登录 + +```typescript +import { rwaLogin } from '@biochain/dweb-compat' + +// 一键登录(封装了完整流程) +const { address, publicKey, signcode } = await rwaLogin(systemPublicKey) + +// 发送到 RWA 后端验证 +await rwaBackend.login({ address, publicKey, signcode }) +``` + +### 手动流程 + +```typescript +import { requestCryptoToken, asymmetricEncrypt } from '@biochain/dweb-compat' + +// 1. 请求授权 +const { tokenId, sessionSecret } = await requestCryptoToken( + ['asymmetricEncrypt'], + '5min', + address +) + +// 2. 执行加密 +const { result, publicKey } = await asymmetricEncrypt( + tokenId, + sessionSecret, + 'data to encrypt', + recipientPublicKey +) +``` + +--- + +## 相关文档 + +- [密钥管理](./01-Key-Management.md) +- [身份认证](./02-Authentication.md) +- [DWEB 授权](./03-DWEB-Authorization.md) diff --git a/docs/white-book/08-Security-Ref/README.md b/docs/white-book/08-Security-Ref/README.md index 4f7b57ca5..b09bd6692 100644 --- a/docs/white-book/08-Security-Ref/README.md +++ b/docs/white-book/08-Security-Ref/README.md @@ -10,6 +10,7 @@ | [02-Authentication](./02-Authentication.md) | 图案锁、生物识别、自动锁定 | | [03-DWEB-Authorization](./03-DWEB-Authorization.md) | Plaoc 协议、地址授权、签名授权 | | [04-Security-Audit](./04-Security-Audit.md) | 审计清单、攻击防护、合规要求 | +| [05-CryptoBox-Authorization](./05-CryptoBox-Authorization.md) | Crypto Box 黑盒授权、Token 机制 | --- diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png index c8a4d1463..683268c53 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png index 63f6e1341..2efb829f8 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png index 54e94e531..41007af7d 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png index 72f8222b6..236984e7d 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png index 537119df8..ef9c12ead 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png index 7eae99aa2..9747131b9 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png index b7c545ead..91e6522d8 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png index 70dfd57d5..9e6f096f7 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png index e9abdb279..4c32d7a85 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png index 1bc392826..58c6daca6 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png index f2e5c86c5..1e908452f 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png and b/e2e/__screenshots__/Desktop-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png index 92d08fed4..944ff73ac 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png index c45f804ab..cfb389757 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02b-ecosystem-my-tab.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png index 3aac61901..dc384c5ce 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02d-context-menu-open.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png index e0f357280..558c1565d 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/02e-context-menu-detail.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/03-wallet-picker.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/03-wallet-picker.png index 70905aa92..42c4cee4e 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/03-wallet-picker.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/03-wallet-picker.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png index 078501219..10f0ab63f 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/04-signing-confirm.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png index ab34954f2..19416b580 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/06-transfer-confirm.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/08-multi-wallet-picker.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/08-multi-wallet-picker.png index 4e35b3466..4bf9960c8 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/08-multi-wallet-picker.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/08-multi-wallet-picker.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png index 341021a38..201fec29d 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/10-long-message-signing.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png index 13f53f31d..05fc1e02d 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/11-large-amount-transfer.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png index ab87cc55f..02b7ab8ec 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/12-small-amount-transfer.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png index 63eca1f26..4228fefc0 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/15-hex-data-warning.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png index 71d40f7d8..f264563b7 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png and b/e2e/__screenshots__/Mobile-Chrome/ecosystem-miniapp.mock.spec.ts/16-miniapp-detail.png differ diff --git a/e2e/__screenshots__/Mobile-Safari/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png b/e2e/__screenshots__/Mobile-Safari/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png index a2c02bafc..53e07895c 100644 Binary files a/e2e/__screenshots__/Mobile-Safari/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png and b/e2e/__screenshots__/Mobile-Safari/ecosystem-miniapp.mock.spec.ts/02-ecosystem-tab-content.png differ diff --git a/package.json b/package.json index 04c4cdbc9..fc0239dfa 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "buffer": "^6.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "ed2curve": "^0.3.0", "i18next": "^25.7.1", "idb": "^8.0.3", "jsqr": "^1.4.0", @@ -123,6 +124,7 @@ "swiper": "^12.0.3", "tailwind-merge": "^3.4.0", "tw-animate-css": "^1.4.0", + "tweetnacl": "^1.0.3", "vaul": "^1.1.2", "viem": "^2.43.3", "yargs": "^18.0.0", @@ -145,6 +147,7 @@ "@testing-library/user-event": "^14.6.1", "@types/big.js": "^6.2.2", "@types/bun": "^1.3.5", + "@types/ed2curve": "^0.2.4", "@types/lodash": "^4.17.21", "@types/node": "^24.10.1", "@types/qrcode": "^1.5.6", @@ -165,6 +168,7 @@ "eslint-plugin-unused-imports": "^4.3.0", "fake-indexeddb": "^6.2.5", "jsdom": "^27.2.0", + "jszip": "^3.10.1", "oxlint": "^1.39.0", "playwright": "^1.57.0", "prettier": "^3.7.4", @@ -174,6 +178,7 @@ "semver": "^7.7.3", "shadcn": "^3.6.1", "sharp": "^0.34.5", + "sirv": "^3.0.2", "ssh2-sftp-client": "^12.0.1", "storybook": "^10.1.4", "tailwindcss": "^4.0.0", diff --git a/packages/bio-sdk/src/chain-id.ts b/packages/bio-sdk/src/chain-id.ts index fdcd5e4cf..a40e98283 100644 --- a/packages/bio-sdk/src/chain-id.ts +++ b/packages/bio-sdk/src/chain-id.ts @@ -33,15 +33,6 @@ export const API_CHAIN_TO_KEYAPP: Record = { bfchain: 'bfchain', } as const -/** KeyApp chain ID to display name */ -export const CHAIN_DISPLAY_NAMES: Record = { - ethereum: 'Ethereum', - binance: 'BNB Smart Chain', - tron: 'Tron', - bfmeta: 'BFMeta', - bfchain: 'BFChain', -} as const - /** * Convert decimal chain ID to hex string (EIP-155 format) * @example toHexChainId(56) => '0x38' diff --git a/packages/dweb-compat/package.json b/packages/dweb-compat/package.json new file mode 100644 index 000000000..4e30b5b4d --- /dev/null +++ b/packages/dweb-compat/package.json @@ -0,0 +1,34 @@ +{ + "name": "@biochain/dweb-compat", + "version": "0.1.0", + "description": "dweb-plaoc 到 KeyApp bio provider 的兼容适配器", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./is-dweb": { + "types": "./src/is-dweb.ts", + "import": "./src/is-dweb.ts" + }, + "./plugins-stub": { + "types": "./src/plugins-stub.ts", + "import": "./src/plugins-stub.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit", + "typecheck:run": "tsc --noEmit", + "test": "vitest", + "test:run": "vitest run --passWithNoTests", + "test:storybook": "echo 'SDK has no storybook'" + }, + "peerDependencies": {}, + "devDependencies": { + "typescript": "^5.9.3", + "vitest": "^4.0.0" + } +} \ No newline at end of file diff --git a/packages/dweb-compat/src/address.ts b/packages/dweb-compat/src/address.ts new file mode 100644 index 000000000..5219161ba --- /dev/null +++ b/packages/dweb-compat/src/address.ts @@ -0,0 +1,79 @@ +/** + * 地址授权适配 + * + * 将 dweb 的 getWalleterAddresss 转换为 KeyApp 的 bio_requestAccounts + */ + +import { bioRequest } from './bridge' +import type { $WalletGetAddressResponse, $CHAIN_NAME } from './types' + +/** KeyApp bio_accounts 返回的账户信息 */ +interface BioAccountInfo { + address: string + chain: string + publicKey?: string +} + +/** + * 获取钱包地址授权 + * + * 对应 dweb 的 getWalleterAddresss(mmid) + * 转换为 KeyApp 的 bio_requestAccounts + bio_accounts + */ +export async function getWalleterAddresss( + _mmid: `${string}.dweb` +): Promise<$WalletGetAddressResponse | null> { + try { + // 1. 请求账户授权 + const addresses = await bioRequest('bio_requestAccounts') + + if (!addresses || addresses.length === 0) { + return null + } + + // 2. 获取完整账户信息 + let accounts: BioAccountInfo[] + try { + accounts = await bioRequest('bio_accounts') + } catch { + // 如果 bio_accounts 失败,使用地址构造基本信息 + accounts = addresses.map(addr => ({ address: addr, chain: 'BFMeta' })) + } + + // 3. 转换为 dweb 格式 + return accounts.map(acc => ({ + name: '', + chainName: acc.chain as $CHAIN_NAME, + address: acc.address, + main: acc.address, // KeyApp 中 main 等于 address + publicKey: acc.publicKey || '', + privateKey: '', // 不暴露私钥 + magic: '', + signMessage: '', + })) + } catch (error) { + console.error('[dweb-compat] getWalleterAddresss error:', error) + return null + } +} + +/** + * 验证地址导入 + * + * 对应 dweb 的 verifyAddressImport + */ +export async function verifyAddressImport( + _mmid: `${string}.dweb`, + opts: { address: string; message: string; chainName: $CHAIN_NAME } +): Promise { + try { + const signature = await bioRequest('bio_signMessage', { + address: opts.address, + message: opts.message, + chain: opts.chainName, + }) + return typeof signature === 'string' && signature.length > 0 + } catch { + return false + } +} diff --git a/packages/dweb-compat/src/bridge.ts b/packages/dweb-compat/src/bridge.ts new file mode 100644 index 000000000..918d08ed9 --- /dev/null +++ b/packages/dweb-compat/src/bridge.ts @@ -0,0 +1,68 @@ +/** + * PostMessage Bridge - 与 KeyApp Bio Provider 通信 + */ + +let requestId = 0 + +export interface BioResponse { + success: boolean + result?: T + error?: { code: number; message: string } +} + +/** + * 发送 bio_request 到 KeyApp 宿主 + */ +export function bioRequest(method: string, params?: unknown): Promise { + return new Promise((resolve, reject) => { + const id = `dweb_compat_${++requestId}_${Date.now()}` + + const handler = (event: MessageEvent) => { + const data = event.data + if (data?.type === 'bio_response' && data.id === id) { + window.removeEventListener('message', handler) + if (data.success) { + resolve(data.result as T) + } else { + const error = new Error(data.error?.message || 'Unknown error') + ; (error as Error & { code?: number }).code = data.error?.code + reject(error) + } + } + } + + window.addEventListener('message', handler) + + // 发送请求到父窗口 (KeyApp) + const message = { + type: 'bio_request', + id, + method, + params: params !== undefined ? [params] : [], + } + + // 优先发送到 parent (iframe 模式),否则通过 self (web worker 或独立模式) + const target = window.parent !== window ? window.parent : window + target.postMessage(message, '*') + + // 超时处理 (60 秒) + setTimeout(() => { + window.removeEventListener('message', handler) + reject(new Error(`Request timeout: ${method}`)) + }, 60000) + }) +} + +/** + * 监听来自 KeyApp 的事件 + */ +export function onBioEvent(event: string, callback: (...args: unknown[]) => void): () => void { + const handler = (e: MessageEvent) => { + if (e.data?.type === 'bio_event' && e.data.event === event) { + callback(...(e.data.args || [])) + } + } + + window.addEventListener('message', handler) + return () => window.removeEventListener('message', handler) +} diff --git a/packages/dweb-compat/src/crypto-token.ts b/packages/dweb-compat/src/crypto-token.ts new file mode 100644 index 000000000..796e2c837 --- /dev/null +++ b/packages/dweb-compat/src/crypto-token.ts @@ -0,0 +1,297 @@ +/** + * Crypto Token API 封装 + * + * 提供 Token 授权和加密操作的便捷函数 + */ + +import { bioRequest } from './bridge' + +// ==================== 类型定义 ==================== + +export type CryptoAction = 'asymmetricEncrypt' | 'sign' +export type TokenDuration = '5min' | '30min' | '2hour' | '1day' + +export interface RequestCryptoTokenParams { + actions: CryptoAction[] + duration: TokenDuration + address: string +} + +export interface RequestCryptoTokenResponse { + tokenId: string + sessionSecret: string + expiresAt: number + grantedActions: CryptoAction[] + /** Token 绑定的地址 */ + address: string +} + +export interface AsymmetricEncryptParams { + data: string + recipientPublicKey: string +} + +export interface SignParams { + data: string +} + +export interface CryptoExecuteResponse { + result: string + publicKey: string + /** Token 绑定的地址 */ + address: string +} + +// ==================== Token 操作 ==================== + +/** + * 请求加密操作授权 + * + * 用户需要输入手势密码确认授权 + * + * @param actions 需要的操作权限 + * @param duration 授权时长 + * @param address 使用的地址 + * @param chainId 链 ID(可选,用于 UI 显示) + */ +export async function requestCryptoToken( + actions: CryptoAction[], + duration: TokenDuration, + address: string, + chainId?: string +): Promise { + return bioRequest('bio_requestCryptoToken', { + actions, + duration, + address, + chainId, + }) +} + +// ==================== Token 查询 ==================== + +export interface GetCryptoTokenInfoResponse { + /** Token 是否有效 */ + valid: boolean + /** Token 绑定的地址 */ + address: string + /** 过期时间戳 */ + expiresAt: number + /** 授权的操作列表 */ + actions: CryptoAction[] + /** 无效原因(仅当 valid=false 时) */ + invalidReason?: 'TOKEN_NOT_FOUND' | 'TOKEN_EXPIRED' | 'INVALID_SESSION_SECRET' | 'MINIAPP_MISMATCH' +} + +/** + * 查询 Token 信息 + * + * 用于检查缓存的 Token 是否有效,以及获取 Token 绑定的地址 + */ +export async function getCryptoTokenInfo( + tokenId: string, + sessionSecret: string +): Promise { + return bioRequest('bio_getCryptoTokenInfo', { + tokenId, + sessionSecret, + }) +} + +/** + * 使用 Token 执行非对称加密 + */ +export async function asymmetricEncrypt( + tokenId: string, + sessionSecret: string, + data: string, + recipientPublicKey: string +): Promise { + return bioRequest('bio_cryptoExecute', { + tokenId, + sessionSecret, + action: 'asymmetricEncrypt', + params: { data, recipientPublicKey }, + }) +} + +/** + * 使用 Token 执行签名 + */ +export async function signData( + tokenId: string, + sessionSecret: string, + data: string +): Promise { + return bioRequest('bio_cryptoExecute', { + tokenId, + sessionSecret, + action: 'sign', + params: { data }, + }) +} + +// ==================== Token 缓存 ==================== + +const CRYPTO_TOKEN_CACHE_KEY = 'bio_crypto_token_cache' + +interface CachedToken { + tokenId: string + sessionSecret: string + address: string + expiresAt: number +} + +function getCachedToken(): CachedToken | null { + try { + const cached = localStorage.getItem(CRYPTO_TOKEN_CACHE_KEY) + if (!cached) return null + return JSON.parse(cached) + } catch { + return null + } +} + +function setCachedToken(token: CachedToken): void { + localStorage.setItem(CRYPTO_TOKEN_CACHE_KEY, JSON.stringify(token)) +} + +function clearCachedToken(): void { + localStorage.removeItem(CRYPTO_TOKEN_CACHE_KEY) +} + +// ==================== RWA 登录便捷函数 ==================== + +export interface RwaLoginResult { + /** 钱包地址 */ + address: string + /** 用户公钥 (Buffer) */ + publicKey: Buffer + /** 加密的 signcode (Buffer) */ + signcode: Buffer +} + +/** + * 将各种格式的公钥转换为 hex 字符串 + */ +function normalizeToHex(input: unknown): string { + if (typeof input === 'string') { + return input + } + // { type: 'Buffer', data: [...] } 格式 + if (input && typeof input === 'object' && 'type' in input && 'data' in input) { + const bufferLike = input as { type: string; data: number[] } + if (bufferLike.type === 'Buffer' && Array.isArray(bufferLike.data)) { + return Buffer.from(bufferLike.data).toString('hex') + } + } + // Uint8Array 或 number[] + if (Array.isArray(input) || input instanceof Uint8Array) { + return Buffer.from(input as ArrayLike).toString('hex') + } + // Buffer + if (Buffer.isBuffer(input)) { + return input.toString('hex') + } + throw new Error(`Invalid input format: ${typeof input}`) +} + +/** + * RWA Hub 登录 + * + * 封装了完整的 RWA 登录流程: + * 1. 检查缓存的 Token 是否有效且地址匹配 + * 2. 如果缓存有效,直接使用 + * 3. 否则请求新的加密授权 + * 4. 执行非对称加密生成 signcode + * + * @param systemPublicKey 系统公钥,支持 hex 字符串、Buffer 对象或字节数组 + */ +export async function rwaLogin( + systemPublicKey: unknown +): Promise { + const systemPubKeyHex = normalizeToHex(systemPublicKey) + // BFMeta 相关链的所有可能名称 + const BFMETA_CHAINS = ['bfmeta', 'bfchainv2', 'bioforest', 'bfmchain', 'bfchain'] + + // 1. 获取钱包地址(指定 BFMeta 链) + const accounts = await bioRequest>( + 'bio_requestAccounts', + { chain: 'bfmeta' } // RWA Hub 需要 BFMeta 链 + ) + + if (!accounts || accounts.length === 0) { + throw new Error('No accounts available') + } + + // 找到 BFMeta 系列链的地址 + const account = accounts.find(a => { + const chain = a.chain.toLowerCase() + return BFMETA_CHAINS.includes(chain) + }) + if (!account) { + const chains = accounts.map(a => a.chain).join(', ') + throw new Error(`BFMeta account not found. Available chains: ${chains}`) + } + + // 2. 检查缓存的 Token + let tokenId: string | undefined + let sessionSecret: string | undefined + + const cached = getCachedToken() + + if (cached && cached.address === account.address && cached.expiresAt > Date.now()) { + // 缓存存在且地址匹配,验证 Token 是否仍然有效 + try { + const info = await getCryptoTokenInfo(cached.tokenId, cached.sessionSecret) + if (info.valid && info.address === account.address) { + // Token 有效且地址匹配,复用缓存 + tokenId = cached.tokenId + sessionSecret = cached.sessionSecret + } + } catch { + // 验证失败,需要新 Token + } + } + + if (!tokenId || !sessionSecret) { + // 需要新 Token:地址不匹配、Token 过期或无效 + clearCachedToken() + + // 请求加密授权(用户输入手势密码) + const response = await requestCryptoToken( + ['asymmetricEncrypt'], + '1day', // 默认使用 1 天授权 + account.address, + account.chain + ) + tokenId = response.tokenId + sessionSecret = response.sessionSecret + + // 缓存新 Token + setCachedToken({ + tokenId, + sessionSecret, + address: response.address, + expiresAt: response.expiresAt, + }) + } + + // 3. 执行非对称加密生成 signcode + const timestamp = Date.now().toString() + const { result, publicKey, address } = await asymmetricEncrypt( + tokenId, + sessionSecret, + timestamp, + systemPubKeyHex + ) + + // 返回 Buffer 格式,与 RWA 后端期望一致 + // 注意:使用 asymmetricEncrypt 返回的 address,而不是 account.address + // 这确保了返回的地址与 Token 绑定的地址一致 + return { + address, + publicKey: Buffer.from(publicKey, 'hex'), + signcode: Buffer.from(result, 'hex'), + } +} diff --git a/packages/dweb-compat/src/index.ts b/packages/dweb-compat/src/index.ts new file mode 100644 index 000000000..2f0712493 --- /dev/null +++ b/packages/dweb-compat/src/index.ts @@ -0,0 +1,65 @@ +/** + * @biochain/dweb-compat + * + * dweb-plaoc 到 KeyApp bio provider 的兼容适配器 + * + * 提供与 @nilai/dweb-plaoc 相同的 API 接口, + * 底层通过 postMessage 调用 KeyApp Bio Provider + */ + +// 核心功能 +export { getWalleterAddresss, verifyAddressImport } from './address' +export { getExternalAppData } from './signature' + +// Crypto Token API +export { + requestCryptoToken, + getCryptoTokenInfo, + asymmetricEncrypt, + signData, + rwaLogin, +} from './crypto-token' +export type { + CryptoAction, + TokenDuration, + RequestCryptoTokenParams, + RequestCryptoTokenResponse, + GetCryptoTokenInfoResponse, + AsymmetricEncryptParams, + SignParams, + CryptoExecuteResponse, + RwaLoginResult, +} from './crypto-token' + +// 类型和常量 +export { + $WALLET_PLAOC_PATH, + $WALLET_SIGNATURE_TYPE, + $WALLET_AUTHORIZE_ADDRESS_TYPE, +} from './types' +export type { + $CHAIN_NAME, + $WALLET_SIGNATURE_TRANSFER, + $WALLET_SIGNATURE_MESSAGE, + $WALLET_SIGNATURE_JSON, + $WALLET_SIGNATURE_DESTORY_ASSET, + $WALLET_SIGNATURE_PARAMETER, + $WalletGetAddressResponse, + $WalletSignatureResponse, + DwebAppConfig, +} from './types' + +// 空操作函数 +export { + canOpenUrl, + focusWindow, + appMaximize, + appVersionUpdate, + browserOpen, + restart, + getDwebAppDownUrl, + gotoDwebAppMarketDownLoad, + CorrecDwebAppLang, + listenerBackButton, + dwebAppConfig, +} from './noop' diff --git a/packages/dweb-compat/src/is-dweb.ts b/packages/dweb-compat/src/is-dweb.ts new file mode 100644 index 000000000..df73a6468 --- /dev/null +++ b/packages/dweb-compat/src/is-dweb.ts @@ -0,0 +1,33 @@ +/** + * @plaoc/is-dweb 兼容层 + * + * 在 miniapp 模式下返回 false + */ + +/** + * 判断是否在 dweb 环境 + */ +export function isDweb(): boolean { + return false +} + +/** + * 获取 dweb 大版本 + */ +export function dwebTarget(): number { + return 0 +} + +/** + * 判断是否移动端 + */ +export function isMobile(): boolean { + const nav = navigator as Navigator & { userAgentData?: { mobile: boolean } } + if (typeof navigator !== 'undefined' && nav.userAgentData) { + return !!nav.userAgentData.mobile + } + // 降级到 UA 检测 + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ) +} diff --git a/packages/dweb-compat/src/noop.ts b/packages/dweb-compat/src/noop.ts new file mode 100644 index 000000000..60dc3e0dc --- /dev/null +++ b/packages/dweb-compat/src/noop.ts @@ -0,0 +1,145 @@ +/** + * 空操作函数 + * + * 这些函数在 dweb 环境中有实际功能,但在 miniapp 中不需要 + */ + +import type { DwebAppConfig } from './types' + +/** + * 检查应用是否可打开 + * + * 在 miniapp 模式下总是返回 true,因为钱包功能由宿主提供 + */ +export function canOpenUrl(_mmid: `${string}.dweb`): Promise<{ success: boolean }> { + return Promise.resolve({ success: true }) +} + +/** + * 聚焦窗口 + * + * 在 miniapp 模式下不需要此操作 + */ +export function focusWindow(): Promise { + return Promise.resolve(new Response()) +} + +/** + * 窗口最大化 + * + * 在 miniapp 模式下不需要此操作 + */ +export function appMaximize(): Promise { + return Promise.resolve(new Response()) +} + +/** + * 版本更新 + * + * 在 miniapp 模式下不支持 + */ +export function appVersionUpdate(_metadataUrl: string): Window | null { + console.warn('[dweb-compat] appVersionUpdate is not supported in miniapp mode') + return null +} + +/** + * 打开浏览器 + * + * 在 miniapp 模式下使用 window.open + */ +export function browserOpen(url: string, target?: string): Promise { + return Promise.resolve(window.open(url, target || '_blank')) +} + +/** + * 重启应用 + * + * 在 miniapp 模式下刷新页面 + */ +export function restart(): Promise { + window.location.reload() + return Promise.resolve(true) +} + +/** + * 获取 Dweb App 下载地址 + * + * 在 miniapp 模式下不需要下载钱包 + */ +export function getDwebAppDownUrl(_info: { + marketUrl: string + name: string + applistJson: string +}): Promise<{ downloadUrl?: string; marketUrl?: string }> { + return Promise.resolve({}) +} + +/** + * 跳转到应用商城下载 + * + * 在 miniapp 模式下不需要 + */ +export function gotoDwebAppMarketDownLoad(_info: { + downloadUrl?: string + marketUrl?: string +}): Promise { + console.warn('[dweb-compat] gotoDwebAppMarketDownLoad is not supported in miniapp mode') + return Promise.resolve(null) +} + +/** + * 矫正语言 + */ +export function CorrecDwebAppLang(lang?: string): Promise { + return Promise.resolve(lang || navigator.language) +} + +/** + * 监听硬件返回按钮 + * + * 在 miniapp 模式下使用浏览器历史 API + */ +export function listenerBackButton(callback: () => void): void { + window.addEventListener('popstate', callback) +} + +/** + * DwebApp 配置 + * + * 在 miniapp 模式下返回空配置 + */ +export const dwebAppConfig = { + XPay(_alpha = false): DwebAppConfig { + return { + mmid: 'pay.nikola-x.com.dweb', + name: 'X Pay', + marketUrl: '', + applistJson: '', + } + }, + BIWMeta(_alpha = false): DwebAppConfig { + return { + mmid: 'biw-meta.com.dweb', + name: 'BIWMeta', + marketUrl: '', + applistJson: '', + } + }, + BFMPay(_alpha = false): DwebAppConfig { + return { + mmid: 'pay.bfmeta.info.dweb', + name: 'BFM Pay', + marketUrl: '', + applistJson: '', + } + }, + NiLai(_alpha = false): DwebAppConfig { + return { + mmid: 'www.ni-lai.com.dweb', + name: 'NiLai', + marketUrl: '', + applistJson: '', + } + }, +} diff --git a/packages/dweb-compat/src/plugins-stub.ts b/packages/dweb-compat/src/plugins-stub.ts new file mode 100644 index 000000000..1065081cb --- /dev/null +++ b/packages/dweb-compat/src/plugins-stub.ts @@ -0,0 +1,36 @@ +/** + * @plaoc/plugins 存根 + * + * 在 miniapp 模式下提供空实现 + */ + +// 导出空的 dwebServiceWorker +export const dwebServiceWorker = { + fetch: () => Promise.reject(new Error('dwebServiceWorker is not available in miniapp mode')), + has: () => Promise.resolve(false), + close: () => Promise.resolve(false), + restart: () => Promise.resolve(false), + clearCache: () => Promise.resolve(), +} + +// 导出空的 windowPlugin +export const windowPlugin = { + maximize: () => Promise.resolve(new Response()), + focusWindow: () => Promise.resolve(new Response()), +} + +// 导出空的 configPlugin +export const configPlugin = { + getLang: () => Promise.resolve(navigator.language), + setLang: () => Promise.resolve(true), +} + +// 为了兼容性导出类型 +export class WebSocketIpcBuilder { + constructor(_url: URL, _config: unknown) { } + get ipc() { + return { + request: () => Promise.reject(new Error('WebSocketIpc is not available in miniapp mode')), + } + } +} diff --git a/packages/dweb-compat/src/signature.ts b/packages/dweb-compat/src/signature.ts new file mode 100644 index 000000000..b50c81bfb --- /dev/null +++ b/packages/dweb-compat/src/signature.ts @@ -0,0 +1,195 @@ +/** + * 签名适配 + * + * 将 dweb 的 getExternalAppData(signature, [...]) 转换为 KeyApp 的 bio_* 方法 + */ + +import { bioRequest } from './bridge' +import { + $WALLET_PLAOC_PATH, + $WALLET_SIGNATURE_TYPE, + type $WALLET_SIGNATURE_PARAMETER, + type $WALLET_PLAOC_PATH_RESPONSE, +} from './types' + +/** 转账响应 */ +interface TransferResponse { + txId: string + transaction: string | object +} + +/** + * 调用外部 App 获取数据 + * + * 对应 dweb 的 getExternalAppData(mmid, pathname, search) + */ +export function getExternalAppData( + _mmid: `${string}.dweb`, + pathname: T, + params?: unknown +) { + const dataPromise = executeRequest<$WALLET_PLAOC_PATH_RESPONSE[T]>(pathname, params) + + return { + getData: (): Promise<$WALLET_PLAOC_PATH_RESPONSE[T]> => dataPromise, + abort: (): void => { + // postMessage 请求不支持中止,这里只是兼容接口 + console.warn('[dweb-compat] abort is not supported in miniapp mode') + }, + } +} + +async function executeRequest(pathname: string, params: unknown): Promise { + switch (pathname) { + case $WALLET_PLAOC_PATH.signature: + if (Array.isArray(params)) { + const results = await Promise.all(params.map(handleSignatureParam)) + return results as T + } + throw new Error('Invalid signature params: expected array') + + case $WALLET_PLAOC_PATH.getAddress: + // 地址请求走 address.ts 的 getWalleterAddresss + throw new Error('Use getWalleterAddresss for address requests') + + case $WALLET_PLAOC_PATH.stakeAsset: + // 锻造功能暂不支持 + throw new Error('stakeAsset is not supported in miniapp mode') + + default: + throw new Error(`Unknown pathname: ${pathname}`) + } +} + +async function handleSignatureParam( + param: $WALLET_SIGNATURE_PARAMETER +): Promise { + switch (param.type) { + case $WALLET_SIGNATURE_TYPE.transfer: + return handleTransfer(param) + + case $WALLET_SIGNATURE_TYPE.message: + return handleMessage(param) + + case $WALLET_SIGNATURE_TYPE.json: + return handleJsonSign(param) + + case $WALLET_SIGNATURE_TYPE.destory: + return handleDestroy(param) + + case $WALLET_SIGNATURE_TYPE.assetTypeBalance: + return handleGetBalance(param) + + case $WALLET_SIGNATURE_TYPE.contract: + return handleContract(param) + + default: + console.error('[dweb-compat] Unsupported signature type:', (param as { type: number }).type) + return { error: true, message: `Unsupported signature type: ${(param as { type: number }).type}` } + } +} + +async function handleTransfer(param: { + chainName: string + senderAddress: string + receiveAddress: string + balance: string + assetType?: string + contractInfo?: { contractAddress: string; assetType: string; decimals: number | string } +}): Promise { + const result = await bioRequest<{ txHash: string; transaction?: object }>('bio_sendTransaction', { + from: param.senderAddress, + to: param.receiveAddress, + amount: param.balance, + chain: param.chainName, + asset: param.assetType, + contractInfo: param.contractInfo, + }) + + return { + txId: result.txHash, + transaction: result.transaction || result.txHash, + } +} + +async function handleMessage(param: { + chainName: string + senderAddress: string + message: string +}): Promise { + const signature = await bioRequest('bio_signMessage', { + address: param.senderAddress, + message: param.message, + chain: param.chainName, + }) + return signature +} + +async function handleJsonSign(param: { + chainName: string + senderAddress: string + json: object + jsonInterpolation?: Array<{ path: string; index: 0 | 1 }> +}): Promise { + const result = await bioRequest<{ txId: string; transaction: object }>('bio_signTypedData', { + address: param.senderAddress, + data: param.json, + chain: param.chainName, + interpolation: param.jsonInterpolation, + }) + return result +} + +async function handleDestroy(param: { + chainName: string + senderAddress: string + assetType: string + destoryAmount: string +}): Promise { + const result = await bioRequest<{ txId: string; transaction: object }>('bio_destroyAsset', { + address: param.senderAddress, + chain: param.chainName, + assetType: param.assetType, + amount: param.destoryAmount, + }) + return result +} + +async function handleGetBalance(param: { + chainName: string + senderAddress: string + assetTypes: Array<{ assetType: string; contractAddress?: string }> +}): Promise> { + const result = await bioRequest>( + 'bio_getBalance', + { + address: param.senderAddress, + chain: param.chainName, + assets: param.assetTypes, + } + ) + return result +} + +async function handleContract(param: { + chainName: string + senderAddress: string + receiveAddress: string + methods: string + params: unknown[] + data: string + amount?: string +}): Promise { + // 合约调用通过 bio_signTransaction + const result = await bioRequest<{ txId: string; signedTx: object }>('bio_signTransaction', { + from: param.senderAddress, + to: param.receiveAddress, + chain: param.chainName, + data: param.data, + value: param.amount || '0', + }) + return { + txId: result.txId, + transaction: result.signedTx, + } +} diff --git a/packages/dweb-compat/src/tauri-stub.ts b/packages/dweb-compat/src/tauri-stub.ts new file mode 100644 index 000000000..0c1d1e8d1 --- /dev/null +++ b/packages/dweb-compat/src/tauri-stub.ts @@ -0,0 +1,12 @@ +/** + * Tauri Plugin Opener Stub + * + * 在 miniapp 模式下替代 @tauri-apps/plugin-opener + */ + +/** + * 在浏览器中打开 URL + */ +export async function openUrl(url: string): Promise { + window.open(url, '_blank') +} diff --git a/packages/dweb-compat/src/types.ts b/packages/dweb-compat/src/types.ts new file mode 100644 index 000000000..ba45c313e --- /dev/null +++ b/packages/dweb-compat/src/types.ts @@ -0,0 +1,187 @@ +/** + * dweb-plaoc 类型定义 + * + * 从 @nilai/dweb-plaoc/types.ts 复制并简化 + */ + +/** 链名称类型 */ +export type $CHAIN_NAME = + | 'BFMeta' + | 'BFMetaV2' + | 'BIWMeta' + | 'Ethereum' + | 'Binance' + | 'Tron' + +/** API 路径 */ +export enum $WALLET_PLAOC_PATH { + /** 获取地址 */ + getAddress = '/wallet/authorize/address', + /** 签名 */ + signature = '/wallet/authorize/signature', + /** 锻造 */ + stakeAsset = '/wallet/staking', +} + +/** 地址授权类型 */ +export enum $WALLET_AUTHORIZE_ADDRESS_TYPE { + main = 'main', + network = 'network', + all = 'all', +} + +/** 签名类型 */ +export enum $WALLET_SIGNATURE_TYPE { + /** 消息签名 */ + message = 0, + /** 转账 */ + transfer = 1, + /** 内链:凭证资产转移 */ + bioforestChainCertificateTransfer = 2, + /** JSON 签名 */ + json = 3, + /** 内链:同质化(非同质化)交易 */ + ENTITY = 4, + /** 获取币种余额 */ + assetTypeBalance = 5, + /** 调用智能合约 */ + contract = 6, + /** 内链销毁 */ + destory = 7, +} + +/** 消息签名参数 */ +export interface $WALLET_SIGNATURE_MESSAGE { + type: typeof $WALLET_SIGNATURE_TYPE.message + chainName: $CHAIN_NAME + senderAddress: string + message: string +} + +/** 转账签名参数 */ +export interface $WALLET_SIGNATURE_TRANSFER { + type: typeof $WALLET_SIGNATURE_TYPE.transfer + chainName: $CHAIN_NAME + assetType?: string + senderAddress: string + receiveAddress: string + privateKey?: string + balance: string + fee?: string + contractInfo?: { + contractAddress: string + assetType: string + decimals: number | string + } + remark?: Record +} + +/** JSON 签名参数 */ +export interface $WALLET_SIGNATURE_JSON { + type: typeof $WALLET_SIGNATURE_TYPE.json + chainName: $CHAIN_NAME + senderAddress: string + json: object + jsonInterpolation?: Array<{ + path: string + index: 0 | 1 + }> +} + +/** 销毁资产参数 */ +export interface $WALLET_SIGNATURE_DESTORY_ASSET { + type: typeof $WALLET_SIGNATURE_TYPE.destory + chainName: $CHAIN_NAME + assetType: string + fee?: string + senderAddress: string + destoryAddress?: string + destoryAmount: string + remark?: Record +} + +/** 获取余额参数 */ +export interface $WALLET_ASSETTYPE_BALANCE { + type: typeof $WALLET_SIGNATURE_TYPE.assetTypeBalance + chainName: $CHAIN_NAME + senderAddress: string + assetTypes: Array<{ + assetType: string + contractAddress?: string + }> +} + +/** 合约调用参数 */ +export interface $WALLET_SIGNATURE_CONTRACT { + type: typeof $WALLET_SIGNATURE_TYPE.contract + chainName: $CHAIN_NAME + amount?: string + methods: string + params: unknown[] + senderAddress: string + receiveAddress: string + data: string + fee?: string + binanceGasInfo?: { + gasLimit: number | string + gasPrice: number | string + } + boradcast?: boolean + waitTrsInBlock?: boolean + waitTime?: number +} + +/** 签名参数联合类型 */ +export type $WALLET_SIGNATURE_PARAMETER = + | $WALLET_SIGNATURE_MESSAGE + | $WALLET_SIGNATURE_DESTORY_ASSET + | $WALLET_SIGNATURE_TRANSFER + | $WALLET_SIGNATURE_JSON + | $WALLET_ASSETTYPE_BALANCE + | $WALLET_SIGNATURE_CONTRACT + +/** 地址响应 */ +export type $WalletGetAddressResponse = Array<{ + name: string + chainName: $CHAIN_NAME + address: string + main: string + publicKey: string + privateKey: string + magic: string + signMessage?: string +}> + +/** 签名响应 */ +export type $WalletSignatureResponse = Array< + | string + | object + | { txId: string; transaction: string | object } + | { [assetType: string]: { assetType: string; decimals: number; balance: string; contracts?: string } } + | null + | { error: boolean; message: string } +> + +/** API 响应类型映射 */ +export type $WALLET_PLAOC_PATH_RESPONSE = { + [$WALLET_PLAOC_PATH.getAddress]: $WalletGetAddressResponse | null + [$WALLET_PLAOC_PATH.signature]: $WalletSignatureResponse | null + [$WALLET_PLAOC_PATH.stakeAsset]: { + address: string + orderId: string + stakeAssetType: string + stakeChainName: $CHAIN_NAME + stakeAmount: string + mintAssetType: string + mintChainName: $CHAIN_NAME + mintAmount: string + } | null +} + +/** DwebApp 配置 */ +export interface DwebAppConfig { + mmid: `${string}.dweb` + name: string + marketUrl: string + applistJson: string +} diff --git a/packages/dweb-compat/tsconfig.json b/packages/dweb-compat/tsconfig.json new file mode 100644 index 000000000..5d643f3f5 --- /dev/null +++ b/packages/dweb-compat/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.app.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": [ + "src/**/*" + ], + "exclude": [] +} \ No newline at end of file diff --git a/packages/dweb-compat/vite.config.ts b/packages/dweb-compat/vite.config.ts new file mode 100644 index 000000000..44becc845 --- /dev/null +++ b/packages/dweb-compat/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'jsdom', + globals: true, + }, +}) diff --git a/packages/flow/tsconfig.tsbuildinfo b/packages/flow/tsconfig.tsbuildinfo index 473149bb2..c10509564 100644 --- a/packages/flow/tsconfig.tsbuildinfo +++ b/packages/flow/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.full.d.ts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/typealiases.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/zoderror.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/enumutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/partialutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/standard-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/index.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/standard-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/util.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/versions.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/schemas.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/checks.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/errors.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/parse.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/regexes.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ar.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/az.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/be.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ca.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/cs.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/de.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/en.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/eo.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/es.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fa.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fi.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fr.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fr-ca.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/he.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/hu.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/id.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/it.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ja.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/kh.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ko.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/mk.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ms.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/nl.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/no.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ota.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ps.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/pl.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/pt.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ru.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/sl.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/sv.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ta.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/th.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/tr.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ua.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ur.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/vi.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/zh-cn.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/zh-tw.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/index.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/registries.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/doc.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/function.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/api.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/json-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/to-json-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/index.d.cts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.d.ts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/errors.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/parse.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/schemas.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/checks.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/compat.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/iso.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/coerce.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/external.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/index.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/index.d.cts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/responsemessage.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts","../../node_modules/.pnpm/json-schema-typed@8.0.2/node_modules/json-schema-typed/draft_2020_12.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uritemplate.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.d.ts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.d.cts","./src/common/mcp/base-mcp.ts","./src/common/preferences.schema.ts","./src/common/paths.ts","./src/common/preferences.ts","./src/common/async-context.ts","./src/common/workflow/base-workflow.ts","./src/meta/lib/scanner.ts","./src/meta/meta.mcp.ts","./src/index.ts","../../node_modules/.pnpm/@types+big.js@6.2.2/node_modules/@types/big.js/index.d.ts","../../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/header.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/readable.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/crypto.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/events.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/utility.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/header.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/readable.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/fetch.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/formdata.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/connector.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client-stats.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/errors.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/dispatcher.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-origin.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool-stats.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/handlers.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/balanced-pool.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/h2c-client.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-call-history.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-client.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-pool.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/snapshot-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-errors.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-handler.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/api.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache-interceptor.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/interceptors.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/util.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cookies.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/patch.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/websocket.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/eventsource.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/content-type.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/index.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/streams.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert/strict.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/async_hooks.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/child_process.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/cluster.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/console.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/constants.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/crypto.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dgram.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/domain.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/events.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http2.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/https.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.generated.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/module.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/net.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/os.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/path.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/perf_hooks.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/process.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/punycode.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/querystring.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/repl.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sea.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sqlite.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/consumers.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/web.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/string_decoder.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/test.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tls.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/trace_events.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tty.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/url.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/util.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/v8.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/vm.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/wasi.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/worker_threads.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/zlib.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/index.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/file.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/fetch.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/formdata.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/connector.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/client.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/errors.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/dispatcher.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/global-origin.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/pool-stats.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/pool.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/handlers.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/balanced-pool.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/agent.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-agent.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-client.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-pool.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/mock-errors.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/api.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/cookies.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/patch.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/filereader.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/websocket.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/content-type.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/cache.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/interceptors.d.ts","../../node_modules/.pnpm/undici-types@5.26.5/node_modules/undici-types/index.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/globals.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/s3.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/fetch.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/bun.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/extensions.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/devserver.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/ffi.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/html-rewriter.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/jsc.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/sqlite.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/utils.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/overloads.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/branding.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/messages.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/index.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/test.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/wasm.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/overrides.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/deprecated.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/redis.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/shell.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/serve.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/sql.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/security.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/bundle.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/bun.ns.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/index.d.ts","../../node_modules/.pnpm/@types+bun@1.3.5/node_modules/@types/bun/index.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/common.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/array.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/collection.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/date.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/function.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/lang.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/math.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/number.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/object.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/seq.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/string.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/util.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/index.d.ts","../../node_modules/.pnpm/@types+qrcode@1.5.6/node_modules/@types/qrcode/index.d.ts","../../node_modules/.pnpm/@types+react@19.2.7/node_modules/@types/react/global.d.ts","../../node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","../../node_modules/.pnpm/@types+react@19.2.7/node_modules/@types/react/index.d.ts","../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.7/node_modules/@types/react-dom/index.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/inc.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/semver.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/parse.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/valid.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/clean.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/diff.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/major.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/minor.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/patch.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/sort.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gt.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lt.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/eq.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/neq.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gte.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lte.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/range.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/index.d.ts","../../node_modules/.pnpm/@types+ssh2@1.15.5/node_modules/@types/ssh2/index.d.ts","../../node_modules/.pnpm/@types+ssh2-sftp-client@9.0.6/node_modules/@types/ssh2-sftp-client/index.d.ts","../../node_modules/.pnpm/@types+yargs-parser@21.0.3/node_modules/@types/yargs-parser/index.d.ts","../../node_modules/.pnpm/@types+yargs@17.0.35/node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[135,147,149,152,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[135,147,150,159,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[135,147,151,152,156,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[147,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[135,147,152,154,155,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[135,147,148,150,152,156,157,158,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[147,148,177,231,248,249,263,312,313,314,315,317,328,329,330,331,332,333,334,335],[78,134,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[135,146,147,148,150,151,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[145,146,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[153,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,338],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,342,343,344,345,346,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,343,344,345,346,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,341,342,343,344,345,346,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,344,345,346,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,345,346,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,346,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,347,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,346,348,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,346,347,349,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,346,347,348,350,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,346,347,348,349,351,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,346,347,348,349,350,352],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,340,341,342,343,344,345,346,347,348,349,350,351],[177,228,229,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,230,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,236,248,249,266,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,232,237,242,248,249,251,263,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,232,233,242,248,249,251,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,234,248,249,275,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,235,236,243,248,249,252,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,236,248,249,263,271,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,237,239,242,248,249,251,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,230,231,238,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,239,240,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,241,242,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,230,231,242,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,243,244,248,249,263,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,243,244,248,249,258,263,266,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,223,231,239,242,245,248,249,251,263,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,243,245,246,248,249,251,263,271,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,245,247,248,249,263,271,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[175,176,177,178,179,180,181,182,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,250,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,239,242,248,249,251,263,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,252,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,253,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,230,231,248,249,254,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,256,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,257,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,248,249,258,259,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,258,260,275,277,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,243,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,248,249,263,264,266,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,265,266,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,263,264,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,266,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,267,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,228,231,248,249,263,268,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,242,248,249,269,270,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,269,270,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,236,248,249,251,263,271,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,272,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,251,273,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,245,248,249,257,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,236,248,249,275,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,263,276,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,250,277,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,278,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,236,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,223,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,279,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,223,231,242,244,248,249,254,263,266,274,276,277,279,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,263,280,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,263,281,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,356],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,354,355],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,359,397],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,359,382,397],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,358,397],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,397],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,359],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,359,383,397],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,383,397],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,398],[177,231,242,245,247,248,249,251,263,281,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335,400],[177,231,236,243,245,248,249,271,275,279,311,312,313,314,317,318,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,331,332,333,334,335],[177,231,248,249,312,313,314,315,328,329,330,331,332,333,334,335],[177,231,248,249,311,312,313,315,317,328,329,330,331,332,333,334,335],[177,231,236,248,249,254,263,266,271,275,279,311,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,281,312,313,314,315,316,317,318,319,320,321,327,328,329,330,331,332,333,334,335,336,337],[10,177,178,231,234,236,243,244,248,249,252,266,271,274,280,312,313,314,315,317,328,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,332,333,334,335],[177,231,243,248,249,312,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334],[177,231,248,249,312,313,314,315,317,328,329,330,331,332,334,335],[177,231,248,249,312,313,314,315,317,328,329,330,331,333,334,335],[177,231,248,249,312,313,314,315,317,321,328,329,330,331,332,333,335],[177,231,248,249,312,313,314,315,317,326,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,322,323,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,322,323,324,325,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,322,324,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,322,328,329,330,331,332,333,334,335],[177,231,248,249,312,313,314,315,317,329,330,331,332,333,334,335],[177,231,248,249,274,288,292,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,263,274,288,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,283,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,271,274,285,288,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,251,271,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,281,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,281,283,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,251,274,285,288,312,313,314,315,317,328,329,330,331,332,333,334,335],[173,174,177,231,242,248,249,263,274,284,287,312,313,314,315,317,328,329,330,331,332,333,334,335],[173,177,231,248,249,286,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,266,274,281,284,288,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,281,304,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,281,282,283,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,288,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,282,283,284,285,286,287,288,289,290,292,293,294,295,296,297,298,299,300,301,302,303,305,306,307,308,309,310,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,288,295,296,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,286,288,296,297,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,287,312,313,314,315,317,328,329,330,331,332,333,334,335],[173,177,231,248,249,283,288,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,288,292,296,297,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,292,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,274,286,288,291,312,313,314,315,317,328,329,330,331,332,333,334,335],[173,177,231,248,249,285,286,288,292,295,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,263,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,279,281,283,288,304,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,189,192,195,196,231,248,249,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,231,248,249,263,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,196,231,248,249,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,186,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,190,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,188,189,192,231,248,249,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,186,231,248,249,281,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,188,192,231,248,249,251,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,183,184,185,187,191,231,242,248,249,263,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,200,208,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,184,190,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,217,218,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,184,187,192,231,248,249,266,274,281,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,188,192,231,248,249,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,183,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,186,187,188,190,191,192,193,194,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,218,219,220,221,222,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,210,213,231,239,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,200,201,202,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,190,192,201,203,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,191,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,184,186,192,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,196,201,203,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,196,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,190,192,195,231,248,249,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,184,188,192,200,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,192,210,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,203,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,186,192,217,231,248,249,266,279,281,312,313,314,315,317,328,329,330,331,332,333,334,335],[77,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[68,69,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[66,67,68,70,71,76,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[67,68,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[76,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[68,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[66,67,68,71,72,73,74,75,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[66,67,78,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[134,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[134,138,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[127,134,136,137,138,139,140,141,142,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[143,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[134,136,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[134,137,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[80,82,83,84,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[80,82,84,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[80,82,84,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[80,82,83,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[80,82,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[80,81,82,83,84,85,86,87,127,128,129,130,131,132,133,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[82,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[79,80,81,83,84,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[82,128,132,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[82,83,84,85,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[144,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[84,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[163,165,177,230,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[147,159,160,161,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,248,249,253,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[161,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[163,164,177,231,244,248,249,253,312,313,314,315,317,328,329,330,331,332,333,334,335],[166,177,231,248,249,253,274,312,313,314,315,317,328,329,330,331,332,333,334,335],[162,163,164,165,166,167,168,169,177,231,248,249,312,313,314,315,317,328,329,330,331,332,333,334,335],[177,231,244,248,249,253,312,313,314,315,317,328,329,330,331,332,333,334,335],[162,166,167,177,231,244,248,249,253,274,312,313,314,315,317,328,329,330,331,332,333,334,335]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"d3cfde44f8089768ebb08098c96d01ca260b88bccf238d55eee93f1c620ff5a5","impliedFormat":1},{"version":"293eadad9dead44c6fd1db6de552663c33f215c55a1bfa2802a1bceed88ff0ec","impliedFormat":1},{"version":"08b2fae7b0f553ad9f79faec864b179fc58bc172e295a70943e8585dd85f600c","impliedFormat":1},{"version":"f12edf1672a94c578eca32216839604f1e1c16b40a1896198deabf99c882b340","impliedFormat":1},{"version":"e3498cf5e428e6c6b9e97bd88736f26d6cf147dedbfa5a8ad3ed8e05e059af8a","impliedFormat":1},{"version":"dba3f34531fd9b1b6e072928b6f885aa4d28dd6789cbd0e93563d43f4b62da53","impliedFormat":1},{"version":"f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c","impliedFormat":1},{"version":"e4b03ddcf8563b1c0aee782a185286ed85a255ce8a30df8453aade2188bbc904","impliedFormat":1},{"version":"2329d90062487e1eaca87b5e06abcbbeeecf80a82f65f949fd332cfcf824b87b","impliedFormat":1},{"version":"25b3f581e12ede11e5739f57a86e8668fbc0124f6649506def306cad2c59d262","impliedFormat":1},{"version":"4fdb529707247a1a917a4626bfb6a293d52cd8ee57ccf03830ec91d39d606d6d","impliedFormat":1},{"version":"a9ebb67d6bbead6044b43714b50dcb77b8f7541ffe803046fdec1714c1eba206","impliedFormat":1},{"version":"833e92c058d033cde3f29a6c7603f517001d1ddd8020bc94d2067a3bc69b2a8e","impliedFormat":1},{"version":"309ebd217636d68cf8784cbc3272c16fb94fb8e969e18b6fe88c35200340aef1","impliedFormat":1},{"version":"91cf9887208be8641244827c18e620166edf7e1c53114930b54eaeaab588a5be","impliedFormat":1},{"version":"ef9b6279acc69002a779d0172916ef22e8be5de2d2469ff2f4bb019a21e89de2","impliedFormat":1},{"version":"71623b889c23a332292c85f9bf41469c3f2efa47f81f12c73e14edbcffa270d3","affectsGlobalScope":true,"impliedFormat":1},{"version":"88863d76039cc550f8b7688a213dd051ae80d94a883eb99389d6bc4ce21c8688","impliedFormat":1},{"version":"e9ce511dae7201b833936d13618dff01815a9db2e6c2cc28646e21520c452d6c","impliedFormat":1},{"version":"243649afb10d950e7e83ee4d53bd2fbd615bb579a74cf6c1ce10e64402cdf9bb","impliedFormat":1},{"version":"35575179030368798cbcd50da928a275234445c9a0df32d4a2c694b2b3d20439","impliedFormat":1},{"version":"c939cb12cb000b4ec9c3eca3fe7dee1fe373ccb801237631d9252bad10206d61","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"03268b4d02371bdf514f513797ed3c9eb0840b0724ff6778bda0ef74c35273be","impliedFormat":1},{"version":"3511847babb822e10715a18348d1cbb0dae73c4e4c0a1bcf7cbc12771b310d45","impliedFormat":1},{"version":"80e653fbbec818eecfe95d182dc65a1d107b343d970159a71922ac4491caa0af","impliedFormat":1},{"version":"53f00dc83ccceb8fad22eb3aade64e4bcdb082115f230c8ba3d40f79c835c30e","impliedFormat":1},{"version":"35475931e8b55c4d33bfe3abc79f5673924a0bd4224c7c6108a4e08f3521643c","impliedFormat":1},{"version":"9078205849121a5d37a642949d687565498da922508eacb0e5a0c3de427f0ae5","impliedFormat":1},{"version":"e8f8f095f137e96dc64b56e59556c02f3c31db4b354801d6ae3b90dceae60240","impliedFormat":1},{"version":"451abef2a26cebb6f54236e68de3c33691e3b47b548fd4c8fa05fd84ab2238ff","impliedFormat":1},{"version":"0648a8c200b5544e30677f7f7059b1e384d6cab716c82659716457e3f317ebae","impliedFormat":99},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"41f185713d78f7af0253a339927dc04b485f46210d6bc0691cf908e3e8ded2a1","impliedFormat":1},{"version":"23ee410c645f68bd99717527de1586e3eb826f166d654b74250ad92b27311fde","impliedFormat":1},{"version":"ffc3e1064146c1cafda1b0686ae9679ba1fb706b2f415e057be01614bf918dba","impliedFormat":1},{"version":"995869b1ddf66bbcfdb417f7446f610198dcce3280a0ae5c8b332ed985c01855","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"dca963a986285211cfa75b9bb57914538de29585d34217d03b538e6473ac4c44","impliedFormat":1},{"version":"d8bc0c5487582c6d887c32c92d8b4ffb23310146fcb1d82adf4b15c77f57c4ac","impliedFormat":1},{"version":"8cb31102790372bebfd78dd56d6752913b0f3e2cefbeb08375acd9f5ba737155","impliedFormat":1},{"version":"f17ed72d1b1882ab6dc66d45e699f757d15bba0807af2fc9c3ec98fe367611c1","impliedFormat":99},{"version":"1261246aed09870ea204dd3ab6958463d4a1bb91da9d34ed17615fbe34699440","impliedFormat":99},{"version":"7bb43a0f0180ad87b0a944ef95be8615d4c1d621a93ae503a8fcdee2027243ef","impliedFormat":99},{"version":"ba678532514244768286bdfdc82b33f072d5de4e9d281a75bcccdba9970788d7","impliedFormat":99},{"version":"0b79f95a79497386c50f38bafbbf59154619e51d7bbe5acf61cd376d3c9d77b9","impliedFormat":99},{"version":"5993793a23b298afd20c2e1cd2bf8468cc7e9415d314d0771e93dd8b2e389d28","impliedFormat":99},{"version":"2ac574152c07fe5bfea9ce46e9452a28f849ec11c7bbbdc399b7bd1aeab9455f","impliedFormat":99},{"version":"104fae9b53b5eaa040d9ce626e1bf0b3e6e27d269a899a98a4a28358cdcbc155","impliedFormat":99},{"version":"50a6aa665f3a2e769a4d683f9f74cd15164d0947fb957d8016331b170ab8b643","impliedFormat":99},{"version":"497b23b09c82d778effca827e408d787634d827c7f2fe63544b19f2815ecdd68","impliedFormat":99},{"version":"33aa2f336bb0bc598652ddd1ad3095ef7a14e4dbed9cf829fa9357e989fff31a","impliedFormat":99},{"version":"d691e546590145171d00d78b341bd3ca4844c96eb34f870be84058a1cab585c3","impliedFormat":99},{"version":"c9d12ca3f67129b3ed2b81bf54537c970673cedd05ba28fbeba70c1e8aff684b","impliedFormat":99},{"version":"6f0b69f7afb2ff04a4b73fae6b43476c14349a438881c7a8c3d34cbad2c2bf3b","impliedFormat":99},{"version":"f55b797d46c4268b2e42961be04e99ad10ccbe55f2cb87fc99b82979fb28121f","impliedFormat":99},{"version":"5780b706cece027f0d4444fbb4e1af62dc51e19da7c3d3719f67b22b033859b9","impliedFormat":1},{"version":"419fedaff1d7a523d33f8daf2ff3c8502490cdf0b96a903850942622185d8063","signature":"c840fd88e60c66ecd0ab35146098e3857d5aa84c32912119409b5adc381a5a3f"},{"version":"d483145f86874b7a3a217d6d51a015d5580cccb93eeeb866b07971c8710fc3f3","signature":"21e579aae766b4c2c2dcb5fc58a662ee930469e3007d7c496f483c4f67acc3f6"},{"version":"35d8bc0f79628c8037b980b60b49586cede0160e28ff8353835ae155b1695a2e","signature":"652f8ee7574a708ea856be9932af949cf5e6a8512c85225611bb67c895ebd08a"},{"version":"ecdc258590549137b9200b588273f638cb28527a1ad510aedf4fa3b1f908595c","signature":"b9a20ffc7d5f00472f446b19d6475d40aa748f23a20b3a5d9e398c12d80f30ad"},{"version":"b4e309a4ebf7df636faa363c562967b15c0af990aa98be1f60068b637c29d954","signature":"e5a2d6ec047f50c932d27fe249f9372bb18750dabdb34363cb72d1e629fd6523"},{"version":"b83e9a0357f8ed9faf637589cd6b9c127b71686c1a4a34d9fb817666e94dd254","signature":"b090ec4cb2ba354e6c359d922002d83946982692691649bc33de4801bca39055"},{"version":"ef2c2fbc5126c2e5a31ba18205fc9c609072cf80e83ceeba0ed3623cb7ae7e67","signature":"a8865d7689d8f317deee22cfe473fd5a60c2b506f34d6e8d7d01105cea883926"},{"version":"09980c5276eb71337c5d5f12d924aace6c5ff7e2f6b1ef64be0bb23205fa1bbf","signature":"1420cb78917c741722357fc3ba643cdafa1bffa49cddcd6d1b6aa48af7967930"},{"version":"b059b345153d6fa7d9c6c4162bcf6789429b6da420d8fc4acdc0045142ee7302","signature":"6089bcc6e6e972107cae2d52387db9657a28c8517bf25186d250909214bd5cb3"},{"version":"ef50b93a202c92c16ba0aa66ac03ded00a213eea4c2fa30adbc191a944f76f12","impliedFormat":1},{"version":"4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"7180c03fd3cb6e22f911ce9ba0f8a7008b1a6ddbe88ccf16a9c8140ef9ac1686","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b21e13ed07d0df176ae31d6b7f01f7b17d66dbeb489c0d31d00de2ca14883da","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"f929f0b6b3421a2d34344b0f421f45aeb2c84ad365ebf29d04312023b3accc58","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"d8cf132379078d0974a59df26069689a2d33c7dc826b5be56231841cb2f32e58","impliedFormat":1},{"version":"fbf413fc617837453c878a9174a1f1b383616857a3f8366bc41cf30df4aea7d5","impliedFormat":1},{"version":"148c73ec11318850f571172ceae3e55ce479d850fe18ec8eae0abd99d9f6c319","impliedFormat":1},{"version":"230bdc111d7578276e4a3bb9d075d85c78c6b68f428c3a9935e2eaa10f4ae1f5","impliedFormat":1},{"version":"e8aabbee5e7b9101b03bb4222607d57f38859b8115a8050a4eb91b4ee43a3a73","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"145dcf25fd4967c610c53d93d7bc4dce8fbb1b6dd7935362472d4ae49363c7ba","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","impliedFormat":1},{"version":"9043daec15206650fa119bad6b8d70136021ea7d52673a71f79a87a42ee38d44","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b055dae40c0e27154f109c4ff771ae748db161c503a1687e3d4b9c91ba20de3","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"65faec1b4bd63564aeec33eab9cacfaefd84ce2400f03903a71a1841fbce195f","impliedFormat":1},{"version":"b33b74b97952d9bf4fbd2951dcfbb5136656ddb310ce1c84518aaa77dbca9992","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"72f8936aebf0c4a1adab767b97d34ba7d3a308afcf76de4417b9c16fb92ed548","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"69e0a41d620fb678a899c65e073413b452f4db321b858fe422ad93fd686cd49a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3585d6891e9ea18e07d0755a6d90d71331558ba5dc5561933553209f886db106","affectsGlobalScope":true,"impliedFormat":1},{"version":"86be71cbb0593468644932a6eb96d527cfa600cecfc0b698af5f52e51804451d","impliedFormat":1},{"version":"84dd6b0fd2505135692935599d6606f50a421389e8d4535194bcded307ee5cf2","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"db19ea066fdc5f97df3f769e582ae3000380ab7942e266654bdb1a4650d19eaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"2a034894bf28c220a331c7a0229d33564803abe2ac1b9a5feee91b6b9b6e88ea","impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"54cb85a47d760da1c13c00add10d26b5118280d44d58e6908d8e89abbd9d7725","impliedFormat":1},{"version":"3e4825171442666d31c845aeb47fcd34b62e14041bb353ae2b874285d78482aa","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"a967bfe3ad4e62243eb604bf956101e4c740f5921277c60debaf325c1320bf88","impliedFormat":1},{"version":"e9775e97ac4877aebf963a0289c81abe76d1ec9a2a7778dbe637e5151f25c5f3","impliedFormat":1},{"version":"471e1da5a78350bc55ef8cef24eb3aca6174143c281b8b214ca2beda51f5e04a","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"db3435f3525cd785bf21ec6769bf8da7e8a776be1a99e2e7efb5f244a2ef5fee","impliedFormat":1},{"version":"c3b170c45fc031db31f782e612adf7314b167e60439d304b49e704010e7bafe5","impliedFormat":1},{"version":"40383ebef22b943d503c6ce2cb2e060282936b952a01bea5f9f493d5fb487cc7","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"3a84b7cb891141824bd00ef8a50b6a44596aded4075da937f180c90e362fe5f6","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"33203609eba548914dc83ddf6cadbc0bcb6e8ef89f6d648ca0908ae887f9fcc5","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"e53a3c2a9f624d90f24bf4588aacd223e7bec1b9d0d479b68d2f4a9e6011147f","impliedFormat":1},{"version":"339dc5265ee5ed92e536a93a04c4ebbc2128f45eeec6ed29f379e0085283542c","impliedFormat":1},{"version":"9f0a92164925aa37d4a5d9dd3e0134cff8177208dba55fd2310cd74beea40ee2","impliedFormat":1},{"version":"8bfdb79bf1a9d435ec48d9372dc93291161f152c0865b81fc0b2694aedb4578d","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"d32275be3546f252e3ad33976caf8c5e842c09cb87d468cb40d5f4cf092d1acc","impliedFormat":1},{"version":"4a0c3504813a3289f7fb1115db13967c8e004aa8e4f8a9021b95285502221bd1","impliedFormat":1},{"version":"6e215dac8b234548d91b718f9c07d5b09473cd5cabb29053fcd8be0af190acb6","affectsGlobalScope":true,"impliedFormat":1},{"version":"0da1adb8d70eba31791b5f9203a384a628f9a1b03162bc68306838e842eff203","impliedFormat":1},{"version":"f3d3e999a323c85c8a63ce90c6e4624ff89fe137a0e2508fddc08e0556d08abf","impliedFormat":1},{"version":"a1fdda024d346cd1906d4a1f66c2804217ef88b554946ac7d9b7bcbadcc75f11","impliedFormat":1},{"version":"49ae37a1b5de16f762c8a151eeaec6b558ce3c27251052ef7a361144af42cad4","impliedFormat":1},{"version":"fc9e630f9302d0414ccd6c8ed2706659cff5ae454a56560c6122fa4a3fac5bbd","affectsGlobalScope":true,"impliedFormat":1},{"version":"aa0a44af370a2d7c1aac988a17836f57910a6c52689f52f5b3ac1d4c6cadcb23","impliedFormat":1},{"version":"0ac74c7586880e26b6a599c710b59284a284e084a2bbc82cd40fb3fbfdea71ae","affectsGlobalScope":true,"impliedFormat":1},{"version":"2ce12357dadbb8efc4e4ec4dab709c8071bf992722fc9adfea2fe0bd5b50923f","impliedFormat":1},{"version":"31bd1a31f935276adf90384a35edbd4614018ff008f57d62ffb57ac538e94e51","impliedFormat":1},{"version":"ffd344731abee98a0a85a735b19052817afd2156d97d1410819cd9bcd1bd575e","impliedFormat":1},{"version":"475e07c959f4766f90678425b45cf58ac9b95e50de78367759c1e5118e85d5c3","impliedFormat":1},{"version":"a524ae401b30a1b0814f1bbcdae459da97fa30ae6e22476e506bb3f82e3d9456","impliedFormat":1},{"version":"7375e803c033425e27cb33bae21917c106cb37b508fd242cccd978ef2ee244c7","impliedFormat":1},{"version":"eeb890c7e9218afdad2f30ad8a76b0b0b5161d11ce13b6723879de408e6bc47a","impliedFormat":1},{"version":"561c795984d06b91091780cebeac616e9e41d83240770e1af14e6ec083b713d5","impliedFormat":1},{"version":"dfbcc400ac6d20b941ccc7bd9031b9d9f54e4d495dd79117334e771959df4805","affectsGlobalScope":true,"impliedFormat":1},{"version":"944d65951e33a13068be5cd525ec42bf9bc180263ba0b723fa236970aa21f611","affectsGlobalScope":true,"impliedFormat":1},{"version":"6b386c7b6ce6f369d18246904fa5eac73566167c88fb6508feba74fa7501a384","affectsGlobalScope":true,"impliedFormat":1},{"version":"592a109e67b907ffd2078cd6f727d5c326e06eaada169eef8fb18546d96f6797","impliedFormat":1},{"version":"f2eb1e35cae499d57e34b4ac3650248776fe7dbd9a3ec34b23754cfd8c22fceb","impliedFormat":1},{"version":"fbed43a6fcf5b675f5ec6fc960328114777862b58a2bb19c109e8fc1906caa09","impliedFormat":1},{"version":"9e98bd421e71f70c75dae7029e316745c89fa7b8bc8b43a91adf9b82c206099c","impliedFormat":1},{"version":"fc803e6b01f4365f71f51f9ce13f71396766848204d4f7a1b2b6154434b84b15","impliedFormat":1},{"version":"f3afcc0d6f77a9ca2d2c5c92eb4b89cd38d6fa4bdc1410d626bd701760a977ec","impliedFormat":1},{"version":"c8109fe76467db6e801d0edfbc50e6826934686467c9418ce6b246232ce7f109","affectsGlobalScope":true,"impliedFormat":1},{"version":"e6f803e4e45915d58e721c04ec17830c6e6678d1e3e00e28edf3d52720909cea","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be812b06e518320ba82e2aff3ac2ca37370a9df917db708f081b9043fa3315","impliedFormat":1},{"version":"380b919bfa0516118edaf25b99e45f855e7bc3fd75ce4163a1cfe4a666388804","impliedFormat":1},{"version":"98acc316756389efdc925de9169c826e4c40a6290fd0ed96b2d5a511b900b486","impliedFormat":1},{"version":"fcf79300e5257a23ed3bacaa6861d7c645139c6f7ece134d15e6669447e5e6db","impliedFormat":1},{"version":"187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","impliedFormat":1},{"version":"aa2c18a1b5a086bbcaae10a4efba409cc95ba7287d8cf8f2591b53704fea3dea","impliedFormat":1},{"version":"b88749bdb18fc1398370e33aa72bc4f88274118f4960e61ce26605f9b33c5ba2","impliedFormat":1},{"version":"0244119dbcbcf34faf3ffdae72dab1e9bc2bc9efc3c477b2240ffa94af3bca56","impliedFormat":1},{"version":"00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","impliedFormat":1},{"version":"a873c50d3e47c21aa09fbe1e2023d9a44efb07cc0cb8c72f418bf301b0771fd3","impliedFormat":1},{"version":"7c14ccd2eaa82619fffc1bfa877eb68a012e9fb723d07ee98db451fadb618906","impliedFormat":1},{"version":"49c36529ee09ea9ce19525af5bb84985ea8e782cb7ee8c493d9e36d027a3d019","impliedFormat":1},{"version":"df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","impliedFormat":1},{"version":"4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","impliedFormat":1},{"version":"006d8ff9a051d61b0887b594b1e76c73314bb1a6fe39026867418937ea2259b3","impliedFormat":1},{"version":"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"5e76305d58bcdc924ff2bf14f6a9dc2aa5441ed06464b7e7bd039e611d66a89b","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"4371055bb001f40596e2e236b27583e13bf11e75d937962f8947d56519237fb8","impliedFormat":1},{"version":"d58caa8f60cc81409121aeaf3e9bfd6306fd3371026eaf94c6ab5a8e98086e4e","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[[162,170]],"options":{"allowImportingTsExtensions":true,"composite":true,"declaration":true,"declarationMap":true,"downlevelIteration":true,"module":99,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"target":9},"referencedMap":[[150,1],[158,2],[155,3],[149,4],[146,5],[156,6],[159,7],[160,8],[135,9],[152,10],[151,4],[148,4],[157,5],[147,11],[154,12],[171,5],[339,13],[341,14],[342,15],[340,16],[343,17],[344,18],[345,19],[346,20],[347,21],[348,22],[349,23],[350,24],[351,25],[352,26],[228,27],[229,27],[230,28],[177,29],[231,30],[232,31],[233,32],[175,5],[234,33],[235,34],[236,35],[237,36],[238,37],[239,38],[240,38],[241,39],[242,40],[243,41],[244,42],[178,5],[176,5],[245,43],[246,44],[247,45],[281,46],[248,47],[249,5],[250,48],[251,49],[252,50],[253,51],[254,52],[255,53],[256,54],[257,55],[258,56],[259,56],[260,57],[261,5],[262,58],[263,59],[265,60],[264,61],[266,62],[267,63],[268,64],[269,65],[270,66],[271,67],[272,68],[273,69],[274,70],[275,71],[276,72],[277,73],[278,74],[179,5],[180,75],[181,5],[182,5],[224,76],[225,77],[226,5],[227,62],[279,78],[280,79],[353,80],[357,81],[354,5],[356,82],[382,83],[383,84],[359,85],[362,86],[380,83],[381,83],[371,83],[370,87],[368,83],[363,83],[376,83],[374,83],[378,83],[358,83],[375,83],[379,83],[364,83],[365,83],[377,83],[360,83],[366,83],[367,83],[369,83],[373,83],[384,88],[372,83],[361,83],[397,89],[396,5],[391,88],[393,90],[392,88],[385,88],[386,88],[388,88],[390,88],[394,90],[395,90],[387,90],[389,90],[399,91],[398,92],[400,5],[401,93],[172,5],[315,94],[337,5],[336,5],[330,95],[317,96],[316,5],[314,97],[318,5],[312,98],[319,5],[338,99],[320,5],[329,100],[331,101],[313,102],[335,103],[333,104],[332,105],[334,106],[321,5],[327,107],[324,108],[326,109],[325,110],[323,111],[322,5],[328,112],[355,5],[153,5],[63,5],[64,5],[12,5],[10,5],[11,5],[16,5],[15,5],[2,5],[17,5],[18,5],[19,5],[20,5],[21,5],[22,5],[23,5],[24,5],[3,5],[25,5],[26,5],[4,5],[27,5],[31,5],[28,5],[29,5],[30,5],[32,5],[33,5],[34,5],[5,5],[35,5],[36,5],[37,5],[38,5],[6,5],[42,5],[39,5],[40,5],[41,5],[43,5],[7,5],[44,5],[49,5],[50,5],[45,5],[46,5],[47,5],[48,5],[8,5],[54,5],[51,5],[52,5],[53,5],[55,5],[9,5],[56,5],[65,5],[57,5],[58,5],[60,5],[59,5],[1,5],[61,5],[62,5],[14,5],[13,5],[295,113],[302,114],[294,113],[309,115],[286,116],[285,117],[308,118],[303,119],[306,120],[288,121],[287,122],[283,123],[282,118],[305,124],[284,125],[289,126],[290,5],[293,126],[173,5],[311,127],[310,126],[297,128],[298,129],[300,130],[296,131],[299,132],[304,118],[291,133],[292,134],[301,135],[174,136],[307,137],[200,138],[212,139],[198,140],[213,136],[222,141],[189,142],[190,143],[188,117],[221,118],[216,144],[220,145],[192,146],[209,147],[191,148],[219,149],[186,150],[187,144],[193,151],[194,5],[199,152],[197,151],[184,153],[223,154],[214,155],[203,156],[202,151],[204,157],[207,158],[201,159],[205,160],[217,118],[195,161],[196,162],[208,163],[185,136],[211,164],[210,151],[206,165],[215,5],[183,5],[218,166],[161,167],[70,168],[77,169],[72,5],[73,5],[71,170],[74,171],[66,5],[67,5],[78,167],[69,172],[75,5],[76,173],[68,174],[139,175],[142,176],[140,176],[136,175],[143,177],[144,178],[141,176],[137,179],[138,180],[131,181],[83,182],[85,183],[129,5],[84,184],[130,185],[134,186],[132,5],[86,182],[87,5],[128,187],[82,188],[79,5],[133,189],[80,190],[81,5],[145,191],[88,192],[89,192],[90,192],[91,192],[92,192],[93,192],[94,192],[95,192],[96,192],[97,192],[98,192],[100,192],[99,192],[101,192],[102,192],[103,192],[127,193],[104,192],[105,192],[106,192],[107,192],[108,192],[109,192],[110,192],[111,192],[112,192],[114,192],[113,192],[115,192],[116,192],[117,192],[118,192],[119,192],[120,192],[121,192],[122,192],[123,192],[124,192],[125,192],[126,192],[166,194],[162,195],[164,196],[163,197],[165,198],[167,199],[170,200],[168,201],[169,202]],"affectedFilesPendingEmit":[[166,49],[162,49],[164,49],[163,49],[165,49],[167,49],[170,49],[168,49],[169,49]],"emitSignatures":[162,163,164,165,166,167,168,169,170],"version":"5.9.3"} \ No newline at end of file +{"fileNames":["../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.full.d.ts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/typealiases.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/zoderror.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/enumutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/partialutil.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/standard-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/index.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/standard-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/util.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/versions.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/schemas.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/checks.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/errors.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/parse.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/regexes.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ar.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/az.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/be.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ca.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/cs.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/de.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/en.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/eo.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/es.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fa.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fi.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fr.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/fr-ca.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/he.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/hu.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/id.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/it.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ja.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/kh.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ko.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/mk.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ms.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/nl.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/no.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ota.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ps.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/pl.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/pt.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ru.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/sl.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/sv.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ta.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/th.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/tr.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ua.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/ur.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/vi.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/zh-cn.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/zh-tw.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/locales/index.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/registries.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/doc.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/function.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/api.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/json-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/to-json-schema.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/index.d.cts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.d.ts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/errors.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/parse.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/schemas.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/checks.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/compat.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/iso.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/coerce.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/external.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/index.d.cts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/index.d.cts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/responsemessage.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts","../../node_modules/.pnpm/json-schema-typed@8.0.2/node_modules/json-schema-typed/draft_2020_12.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/types.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uritemplate.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts","../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.1_hono@4.11.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.d.ts","../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.d.cts","./src/common/mcp/base-mcp.ts","./src/common/preferences.schema.ts","./src/common/paths.ts","./src/common/preferences.ts","./src/common/async-context.ts","./src/common/workflow/base-workflow.ts","./src/meta/lib/scanner.ts","./src/meta/meta.mcp.ts","./src/index.ts","../../node_modules/.pnpm/@types+big.js@6.2.2/node_modules/@types/big.js/index.d.ts","../../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/header.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/readable.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/globals.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/crypto.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/events.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/utility.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/header.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/readable.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/fetch.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/formdata.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/connector.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client-stats.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/client.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/errors.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/dispatcher.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/global-origin.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool-stats.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/pool.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/handlers.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/balanced-pool.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/h2c-client.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-call-history.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-client.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-pool.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/snapshot-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/mock-errors.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-handler.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/retry-agent.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/api.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache-interceptor.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/interceptors.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/util.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cookies.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/patch.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/websocket.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/eventsource.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/content-type.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/cache.d.ts","../../node_modules/.pnpm/undici-types@7.16.0/node_modules/undici-types/index.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/web-globals/streams.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/assert/strict.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/async_hooks.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/buffer.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/child_process.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/cluster.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/console.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/constants.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/crypto.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dgram.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/dns/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/domain.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/events.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/fs/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/http2.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/https.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/inspector.generated.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/module.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/net.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/os.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/path.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/perf_hooks.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/process.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/punycode.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/querystring.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/readline/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/repl.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sea.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/sqlite.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/consumers.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/stream/web.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/string_decoder.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/test.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/timers/promises.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tls.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/trace_events.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/tty.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/url.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/util.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/v8.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/vm.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/wasi.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/worker_threads.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/zlib.d.ts","../../node_modules/.pnpm/@types+node@24.10.4/node_modules/@types/node/index.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/file.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/fetch.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/formdata.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/connector.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/client.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/errors.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/dispatcher.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/global-origin.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/pool-stats.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/pool.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/handlers.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/balanced-pool.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-client.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-pool.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/mock-errors.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/retry-handler.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/retry-agent.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/api.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/interceptors.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/util.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/cookies.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/patch.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/websocket.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/eventsource.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/filereader.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/content-type.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/cache.d.ts","../../node_modules/.pnpm/undici-types@6.21.0/node_modules/undici-types/index.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/globals.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/s3.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/fetch.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/bun.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/extensions.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/devserver.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/ffi.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/html-rewriter.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/jsc.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/sqlite.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/utils.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/overloads.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/branding.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/messages.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/vendor/expect-type/index.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/test.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/wasm.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/overrides.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/deprecated.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/redis.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/shell.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/serve.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/sql.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/security.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/bundle.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/bun.ns.d.ts","../../node_modules/.pnpm/bun-types@1.3.5/node_modules/bun-types/index.d.ts","../../node_modules/.pnpm/@types+bun@1.3.5/node_modules/@types/bun/index.d.ts","../../node_modules/.pnpm/tweetnacl@1.0.3/node_modules/tweetnacl/nacl.d.ts","../../node_modules/.pnpm/@types+ed2curve@0.2.4/node_modules/@types/ed2curve/index.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/common.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/array.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/collection.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/date.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/function.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/lang.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/math.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/number.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/object.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/seq.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/string.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/common/util.d.ts","../../node_modules/.pnpm/@types+lodash@4.17.21/node_modules/@types/lodash/index.d.ts","../../node_modules/.pnpm/@types+qrcode@1.5.6/node_modules/@types/qrcode/index.d.ts","../../node_modules/.pnpm/@types+react@19.2.7/node_modules/@types/react/global.d.ts","../../node_modules/.pnpm/csstype@3.2.3/node_modules/csstype/index.d.ts","../../node_modules/.pnpm/@types+react@19.2.7/node_modules/@types/react/index.d.ts","../../node_modules/.pnpm/@types+react-dom@19.2.3_@types+react@19.2.7/node_modules/@types/react-dom/index.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/inc.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/semver.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/parse.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/valid.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/clean.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/diff.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/major.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/minor.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/patch.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/sort.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gt.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lt.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/eq.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/neq.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/gte.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/lte.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/classes/range.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/.pnpm/@types+semver@7.7.1/node_modules/@types/semver/index.d.ts","../../node_modules/.pnpm/@types+ssh2@1.15.5/node_modules/@types/ssh2/index.d.ts","../../node_modules/.pnpm/@types+ssh2-sftp-client@9.0.6/node_modules/@types/ssh2-sftp-client/index.d.ts","../../node_modules/.pnpm/@types+yargs-parser@21.0.3/node_modules/@types/yargs-parser/index.d.ts","../../node_modules/.pnpm/@types+yargs@17.0.35/node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[135,147,149,152,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[135,147,150,159,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[135,147,151,152,156,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[147,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[135,147,152,154,155,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[135,147,148,150,152,156,157,158,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[147,148,177,231,248,249,263,317,318,319,320,322,333,334,335,336,337,338,339,340],[78,134,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[135,146,147,148,150,151,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[145,146,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[153,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,343],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,345],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,349,350,351,352,353,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,350,351,352,353,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,348,349,350,351,352,353,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,351,352,353,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,352,353,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,353,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,354,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,353,355,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,353,354,356,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,353,354,355,357,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,353,354,355,356,358,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,353,354,355,356,357,359],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,347,348,349,350,351,352,353,354,355,356,357,358],[177,228,229,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,230,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,236,248,249,266,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,232,237,242,248,249,251,263,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,232,233,242,248,249,251,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,234,248,249,275,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,235,236,243,248,249,252,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,236,248,249,263,271,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,237,239,242,248,249,251,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,230,231,238,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,239,240,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,241,242,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,230,231,242,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,243,244,248,249,263,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,243,244,248,249,258,263,266,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,223,231,239,242,245,248,249,251,263,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,243,245,246,248,249,251,263,271,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,245,247,248,249,263,271,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[175,176,177,178,179,180,181,182,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,250,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,239,242,248,249,251,263,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,252,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,253,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,230,231,248,249,254,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,256,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,257,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,248,249,258,259,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,258,260,275,277,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,243,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,248,249,263,264,266,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,265,266,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,263,264,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,266,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,267,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,228,231,248,249,263,268,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,242,248,249,269,270,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,269,270,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,236,248,249,251,263,271,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,272,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,251,273,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,245,248,249,257,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,236,248,249,275,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,263,276,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,250,277,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,278,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,236,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,223,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,279,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,223,231,242,244,248,249,254,263,266,274,276,277,279,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,263,280,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,263,281,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,363],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,361,362],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,366,404],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,366,389,404],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,365,404],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,404],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,366],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,366,390,404],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,390,404],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,405],[177,231,242,245,247,248,249,251,263,281,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340,407],[177,231,236,243,245,248,249,271,275,279,316,317,318,319,322,323,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,336,337,338,339,340],[177,231,248,249,317,318,319,320,333,334,335,336,337,338,339,340],[177,231,248,249,316,317,318,320,322,333,334,335,336,337,338,339,340],[177,231,236,248,249,254,263,266,271,275,279,316,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,281,317,318,319,320,321,322,323,324,325,326,332,333,334,335,336,337,338,339,340,341,342],[10,177,178,231,234,236,243,244,248,249,252,266,271,274,280,317,318,319,320,322,333,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,337,338,339,340],[177,231,243,248,249,317,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339],[177,231,248,249,317,318,319,320,322,333,334,335,336,337,339,340],[177,231,248,249,317,318,319,320,322,333,334,335,336,338,339,340],[177,231,248,249,317,318,319,320,322,326,333,334,335,336,337,338,340],[177,231,248,249,317,318,319,320,322,331,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,327,328,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,327,328,329,330,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,327,329,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,327,333,334,335,336,337,338,339,340],[177,231,248,249,317,318,319,320,322,334,335,336,337,338,339,340],[177,231,248,249,274,288,292,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,263,274,288,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,283,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,271,274,285,288,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,251,271,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,281,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,281,283,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,251,274,285,288,317,318,319,320,322,333,334,335,336,337,338,339,340],[173,174,177,231,242,248,249,263,274,284,287,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,288,295,317,318,319,320,322,333,334,335,336,337,338,339,340],[173,177,231,248,249,286,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,288,309,310,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,266,274,281,284,288,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,281,309,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,281,282,283,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,288,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,282,283,284,285,286,287,288,289,290,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,310,311,312,313,314,315,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,288,303,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,288,295,296,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,286,288,296,297,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,287,317,318,319,320,322,333,334,335,336,337,338,339,340],[173,177,231,248,249,283,288,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,288,292,296,297,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,292,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,274,286,288,291,317,318,319,320,322,333,334,335,336,337,338,339,340],[173,177,231,248,249,285,288,295,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,263,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,279,281,283,288,309,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,189,192,195,196,231,248,249,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,231,248,249,263,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,196,231,248,249,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,186,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,190,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,188,189,192,231,248,249,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,186,231,248,249,281,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,188,192,231,248,249,251,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,183,184,185,187,191,231,242,248,249,263,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,200,208,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,184,190,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,217,218,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,184,187,192,231,248,249,266,274,281,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,188,192,231,248,249,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,183,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,186,187,188,190,191,192,193,194,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,218,219,220,221,222,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,210,213,231,239,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,200,201,202,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,190,192,201,203,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,191,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,184,186,192,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,196,201,203,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,196,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,190,192,195,231,248,249,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,184,188,192,200,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,192,210,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,203,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,186,192,217,231,248,249,266,279,281,317,318,319,320,322,333,334,335,336,337,338,339,340],[77,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[68,69,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[66,67,68,70,71,76,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[67,68,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[76,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[68,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[66,67,68,71,72,73,74,75,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[66,67,78,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[134,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[134,138,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[127,134,136,137,138,139,140,141,142,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[143,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[134,136,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[134,137,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[80,82,83,84,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[80,82,84,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[80,82,84,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[80,82,83,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[80,82,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[80,81,82,83,84,85,86,87,127,128,129,130,131,132,133,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[82,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[79,80,81,83,84,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[82,128,132,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[82,83,84,85,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[144,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[84,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[163,165,177,230,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[147,159,160,161,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,248,249,253,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[161,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[163,164,177,231,244,248,249,253,317,318,319,320,322,333,334,335,336,337,338,339,340],[166,177,231,248,249,253,274,317,318,319,320,322,333,334,335,336,337,338,339,340],[162,163,164,165,166,167,168,169,177,231,248,249,317,318,319,320,322,333,334,335,336,337,338,339,340],[177,231,244,248,249,253,317,318,319,320,322,333,334,335,336,337,338,339,340],[162,166,167,177,231,244,248,249,253,274,317,318,319,320,322,333,334,335,336,337,338,339,340]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"d3cfde44f8089768ebb08098c96d01ca260b88bccf238d55eee93f1c620ff5a5","impliedFormat":1},{"version":"293eadad9dead44c6fd1db6de552663c33f215c55a1bfa2802a1bceed88ff0ec","impliedFormat":1},{"version":"08b2fae7b0f553ad9f79faec864b179fc58bc172e295a70943e8585dd85f600c","impliedFormat":1},{"version":"f12edf1672a94c578eca32216839604f1e1c16b40a1896198deabf99c882b340","impliedFormat":1},{"version":"e3498cf5e428e6c6b9e97bd88736f26d6cf147dedbfa5a8ad3ed8e05e059af8a","impliedFormat":1},{"version":"dba3f34531fd9b1b6e072928b6f885aa4d28dd6789cbd0e93563d43f4b62da53","impliedFormat":1},{"version":"f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c","impliedFormat":1},{"version":"e4b03ddcf8563b1c0aee782a185286ed85a255ce8a30df8453aade2188bbc904","impliedFormat":1},{"version":"2329d90062487e1eaca87b5e06abcbbeeecf80a82f65f949fd332cfcf824b87b","impliedFormat":1},{"version":"25b3f581e12ede11e5739f57a86e8668fbc0124f6649506def306cad2c59d262","impliedFormat":1},{"version":"4fdb529707247a1a917a4626bfb6a293d52cd8ee57ccf03830ec91d39d606d6d","impliedFormat":1},{"version":"a9ebb67d6bbead6044b43714b50dcb77b8f7541ffe803046fdec1714c1eba206","impliedFormat":1},{"version":"833e92c058d033cde3f29a6c7603f517001d1ddd8020bc94d2067a3bc69b2a8e","impliedFormat":1},{"version":"309ebd217636d68cf8784cbc3272c16fb94fb8e969e18b6fe88c35200340aef1","impliedFormat":1},{"version":"91cf9887208be8641244827c18e620166edf7e1c53114930b54eaeaab588a5be","impliedFormat":1},{"version":"ef9b6279acc69002a779d0172916ef22e8be5de2d2469ff2f4bb019a21e89de2","impliedFormat":1},{"version":"71623b889c23a332292c85f9bf41469c3f2efa47f81f12c73e14edbcffa270d3","affectsGlobalScope":true,"impliedFormat":1},{"version":"88863d76039cc550f8b7688a213dd051ae80d94a883eb99389d6bc4ce21c8688","impliedFormat":1},{"version":"e9ce511dae7201b833936d13618dff01815a9db2e6c2cc28646e21520c452d6c","impliedFormat":1},{"version":"243649afb10d950e7e83ee4d53bd2fbd615bb579a74cf6c1ce10e64402cdf9bb","impliedFormat":1},{"version":"35575179030368798cbcd50da928a275234445c9a0df32d4a2c694b2b3d20439","impliedFormat":1},{"version":"c939cb12cb000b4ec9c3eca3fe7dee1fe373ccb801237631d9252bad10206d61","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"26384fb401f582cae1234213c3dc75fdc80e3d728a0a1c55b405be8a0c6dddbe","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"b42d3651103a532f7492e719a828647af97306b2356ae757ebb7f17f4a8c41e5","impliedFormat":1},{"version":"03268b4d02371bdf514f513797ed3c9eb0840b0724ff6778bda0ef74c35273be","impliedFormat":1},{"version":"3511847babb822e10715a18348d1cbb0dae73c4e4c0a1bcf7cbc12771b310d45","impliedFormat":1},{"version":"80e653fbbec818eecfe95d182dc65a1d107b343d970159a71922ac4491caa0af","impliedFormat":1},{"version":"53f00dc83ccceb8fad22eb3aade64e4bcdb082115f230c8ba3d40f79c835c30e","impliedFormat":1},{"version":"35475931e8b55c4d33bfe3abc79f5673924a0bd4224c7c6108a4e08f3521643c","impliedFormat":1},{"version":"9078205849121a5d37a642949d687565498da922508eacb0e5a0c3de427f0ae5","impliedFormat":1},{"version":"e8f8f095f137e96dc64b56e59556c02f3c31db4b354801d6ae3b90dceae60240","impliedFormat":1},{"version":"451abef2a26cebb6f54236e68de3c33691e3b47b548fd4c8fa05fd84ab2238ff","impliedFormat":1},{"version":"0648a8c200b5544e30677f7f7059b1e384d6cab716c82659716457e3f317ebae","impliedFormat":99},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"41f185713d78f7af0253a339927dc04b485f46210d6bc0691cf908e3e8ded2a1","impliedFormat":1},{"version":"23ee410c645f68bd99717527de1586e3eb826f166d654b74250ad92b27311fde","impliedFormat":1},{"version":"ffc3e1064146c1cafda1b0686ae9679ba1fb706b2f415e057be01614bf918dba","impliedFormat":1},{"version":"995869b1ddf66bbcfdb417f7446f610198dcce3280a0ae5c8b332ed985c01855","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"dca963a986285211cfa75b9bb57914538de29585d34217d03b538e6473ac4c44","impliedFormat":1},{"version":"d8bc0c5487582c6d887c32c92d8b4ffb23310146fcb1d82adf4b15c77f57c4ac","impliedFormat":1},{"version":"8cb31102790372bebfd78dd56d6752913b0f3e2cefbeb08375acd9f5ba737155","impliedFormat":1},{"version":"f17ed72d1b1882ab6dc66d45e699f757d15bba0807af2fc9c3ec98fe367611c1","impliedFormat":99},{"version":"1261246aed09870ea204dd3ab6958463d4a1bb91da9d34ed17615fbe34699440","impliedFormat":99},{"version":"7bb43a0f0180ad87b0a944ef95be8615d4c1d621a93ae503a8fcdee2027243ef","impliedFormat":99},{"version":"ba678532514244768286bdfdc82b33f072d5de4e9d281a75bcccdba9970788d7","impliedFormat":99},{"version":"0b79f95a79497386c50f38bafbbf59154619e51d7bbe5acf61cd376d3c9d77b9","impliedFormat":99},{"version":"5993793a23b298afd20c2e1cd2bf8468cc7e9415d314d0771e93dd8b2e389d28","impliedFormat":99},{"version":"2ac574152c07fe5bfea9ce46e9452a28f849ec11c7bbbdc399b7bd1aeab9455f","impliedFormat":99},{"version":"104fae9b53b5eaa040d9ce626e1bf0b3e6e27d269a899a98a4a28358cdcbc155","impliedFormat":99},{"version":"50a6aa665f3a2e769a4d683f9f74cd15164d0947fb957d8016331b170ab8b643","impliedFormat":99},{"version":"497b23b09c82d778effca827e408d787634d827c7f2fe63544b19f2815ecdd68","impliedFormat":99},{"version":"33aa2f336bb0bc598652ddd1ad3095ef7a14e4dbed9cf829fa9357e989fff31a","impliedFormat":99},{"version":"d691e546590145171d00d78b341bd3ca4844c96eb34f870be84058a1cab585c3","impliedFormat":99},{"version":"c9d12ca3f67129b3ed2b81bf54537c970673cedd05ba28fbeba70c1e8aff684b","impliedFormat":99},{"version":"6f0b69f7afb2ff04a4b73fae6b43476c14349a438881c7a8c3d34cbad2c2bf3b","impliedFormat":99},{"version":"f55b797d46c4268b2e42961be04e99ad10ccbe55f2cb87fc99b82979fb28121f","impliedFormat":99},{"version":"5780b706cece027f0d4444fbb4e1af62dc51e19da7c3d3719f67b22b033859b9","impliedFormat":1},"419fedaff1d7a523d33f8daf2ff3c8502490cdf0b96a903850942622185d8063","d483145f86874b7a3a217d6d51a015d5580cccb93eeeb866b07971c8710fc3f3","35d8bc0f79628c8037b980b60b49586cede0160e28ff8353835ae155b1695a2e","ecdc258590549137b9200b588273f638cb28527a1ad510aedf4fa3b1f908595c","b4e309a4ebf7df636faa363c562967b15c0af990aa98be1f60068b637c29d954","b83e9a0357f8ed9faf637589cd6b9c127b71686c1a4a34d9fb817666e94dd254","ef2c2fbc5126c2e5a31ba18205fc9c609072cf80e83ceeba0ed3623cb7ae7e67","09980c5276eb71337c5d5f12d924aace6c5ff7e2f6b1ef64be0bb23205fa1bbf","b059b345153d6fa7d9c6c4162bcf6789429b6da420d8fc4acdc0045142ee7302",{"version":"ef50b93a202c92c16ba0aa66ac03ded00a213eea4c2fa30adbc191a944f76f12","impliedFormat":1},{"version":"4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b21e13ed07d0df176ae31d6b7f01f7b17d66dbeb489c0d31d00de2ca14883da","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"f929f0b6b3421a2d34344b0f421f45aeb2c84ad365ebf29d04312023b3accc58","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"d8cf132379078d0974a59df26069689a2d33c7dc826b5be56231841cb2f32e58","impliedFormat":1},{"version":"fbf413fc617837453c878a9174a1f1b383616857a3f8366bc41cf30df4aea7d5","impliedFormat":1},{"version":"148c73ec11318850f571172ceae3e55ce479d850fe18ec8eae0abd99d9f6c319","impliedFormat":1},{"version":"230bdc111d7578276e4a3bb9d075d85c78c6b68f428c3a9935e2eaa10f4ae1f5","impliedFormat":1},{"version":"e8aabbee5e7b9101b03bb4222607d57f38859b8115a8050a4eb91b4ee43a3a73","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"145dcf25fd4967c610c53d93d7bc4dce8fbb1b6dd7935362472d4ae49363c7ba","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","impliedFormat":1},{"version":"9043daec15206650fa119bad6b8d70136021ea7d52673a71f79a87a42ee38d44","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b055dae40c0e27154f109c4ff771ae748db161c503a1687e3d4b9c91ba20de3","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"65faec1b4bd63564aeec33eab9cacfaefd84ce2400f03903a71a1841fbce195f","impliedFormat":1},{"version":"b33b74b97952d9bf4fbd2951dcfbb5136656ddb310ce1c84518aaa77dbca9992","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"72f8936aebf0c4a1adab767b97d34ba7d3a308afcf76de4417b9c16fb92ed548","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"69e0a41d620fb678a899c65e073413b452f4db321b858fe422ad93fd686cd49a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3585d6891e9ea18e07d0755a6d90d71331558ba5dc5561933553209f886db106","affectsGlobalScope":true,"impliedFormat":1},{"version":"86be71cbb0593468644932a6eb96d527cfa600cecfc0b698af5f52e51804451d","impliedFormat":1},{"version":"84dd6b0fd2505135692935599d6606f50a421389e8d4535194bcded307ee5cf2","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"db19ea066fdc5f97df3f769e582ae3000380ab7942e266654bdb1a4650d19eaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"2a034894bf28c220a331c7a0229d33564803abe2ac1b9a5feee91b6b9b6e88ea","impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"6e215dac8b234548d91b718f9c07d5b09473cd5cabb29053fcd8be0af190acb6","affectsGlobalScope":true,"impliedFormat":1},{"version":"0da1adb8d70eba31791b5f9203a384a628f9a1b03162bc68306838e842eff203","impliedFormat":1},{"version":"f3d3e999a323c85c8a63ce90c6e4624ff89fe137a0e2508fddc08e0556d08abf","impliedFormat":1},{"version":"a1fdda024d346cd1906d4a1f66c2804217ef88b554946ac7d9b7bcbadcc75f11","impliedFormat":1},{"version":"49ae37a1b5de16f762c8a151eeaec6b558ce3c27251052ef7a361144af42cad4","impliedFormat":1},{"version":"fc9e630f9302d0414ccd6c8ed2706659cff5ae454a56560c6122fa4a3fac5bbd","affectsGlobalScope":true,"impliedFormat":1},{"version":"aa0a44af370a2d7c1aac988a17836f57910a6c52689f52f5b3ac1d4c6cadcb23","impliedFormat":1},{"version":"0ac74c7586880e26b6a599c710b59284a284e084a2bbc82cd40fb3fbfdea71ae","affectsGlobalScope":true,"impliedFormat":1},{"version":"2ce12357dadbb8efc4e4ec4dab709c8071bf992722fc9adfea2fe0bd5b50923f","impliedFormat":1},{"version":"31bd1a31f935276adf90384a35edbd4614018ff008f57d62ffb57ac538e94e51","impliedFormat":1},{"version":"ffd344731abee98a0a85a735b19052817afd2156d97d1410819cd9bcd1bd575e","impliedFormat":1},{"version":"475e07c959f4766f90678425b45cf58ac9b95e50de78367759c1e5118e85d5c3","impliedFormat":1},{"version":"a524ae401b30a1b0814f1bbcdae459da97fa30ae6e22476e506bb3f82e3d9456","impliedFormat":1},{"version":"7375e803c033425e27cb33bae21917c106cb37b508fd242cccd978ef2ee244c7","impliedFormat":1},{"version":"eeb890c7e9218afdad2f30ad8a76b0b0b5161d11ce13b6723879de408e6bc47a","impliedFormat":1},{"version":"561c795984d06b91091780cebeac616e9e41d83240770e1af14e6ec083b713d5","impliedFormat":1},{"version":"dfbcc400ac6d20b941ccc7bd9031b9d9f54e4d495dd79117334e771959df4805","affectsGlobalScope":true,"impliedFormat":1},{"version":"944d65951e33a13068be5cd525ec42bf9bc180263ba0b723fa236970aa21f611","affectsGlobalScope":true,"impliedFormat":1},{"version":"6b386c7b6ce6f369d18246904fa5eac73566167c88fb6508feba74fa7501a384","affectsGlobalScope":true,"impliedFormat":1},{"version":"592a109e67b907ffd2078cd6f727d5c326e06eaada169eef8fb18546d96f6797","impliedFormat":1},{"version":"f2eb1e35cae499d57e34b4ac3650248776fe7dbd9a3ec34b23754cfd8c22fceb","impliedFormat":1},{"version":"fbed43a6fcf5b675f5ec6fc960328114777862b58a2bb19c109e8fc1906caa09","impliedFormat":1},{"version":"9e98bd421e71f70c75dae7029e316745c89fa7b8bc8b43a91adf9b82c206099c","impliedFormat":1},{"version":"fc803e6b01f4365f71f51f9ce13f71396766848204d4f7a1b2b6154434b84b15","impliedFormat":1},{"version":"f3afcc0d6f77a9ca2d2c5c92eb4b89cd38d6fa4bdc1410d626bd701760a977ec","impliedFormat":1},{"version":"c8109fe76467db6e801d0edfbc50e6826934686467c9418ce6b246232ce7f109","affectsGlobalScope":true,"impliedFormat":1},{"version":"e6f803e4e45915d58e721c04ec17830c6e6678d1e3e00e28edf3d52720909cea","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be812b06e518320ba82e2aff3ac2ca37370a9df917db708f081b9043fa3315","impliedFormat":1},{"version":"60592f5ae1b739c9607a99895d4a3ad5c865b16903e4180e50b256e360a4a104","impliedFormat":1},{"version":"2ff42f1493403d55da28c165db1a630afef76c11e474b7744e8b76c969fe0796","impliedFormat":1},{"version":"380b919bfa0516118edaf25b99e45f855e7bc3fd75ce4163a1cfe4a666388804","impliedFormat":1},{"version":"98acc316756389efdc925de9169c826e4c40a6290fd0ed96b2d5a511b900b486","impliedFormat":1},{"version":"fcf79300e5257a23ed3bacaa6861d7c645139c6f7ece134d15e6669447e5e6db","impliedFormat":1},{"version":"187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","impliedFormat":1},{"version":"aa2c18a1b5a086bbcaae10a4efba409cc95ba7287d8cf8f2591b53704fea3dea","impliedFormat":1},{"version":"b88749bdb18fc1398370e33aa72bc4f88274118f4960e61ce26605f9b33c5ba2","impliedFormat":1},{"version":"0244119dbcbcf34faf3ffdae72dab1e9bc2bc9efc3c477b2240ffa94af3bca56","impliedFormat":1},{"version":"00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","impliedFormat":1},{"version":"a873c50d3e47c21aa09fbe1e2023d9a44efb07cc0cb8c72f418bf301b0771fd3","impliedFormat":1},{"version":"7c14ccd2eaa82619fffc1bfa877eb68a012e9fb723d07ee98db451fadb618906","impliedFormat":1},{"version":"49c36529ee09ea9ce19525af5bb84985ea8e782cb7ee8c493d9e36d027a3d019","impliedFormat":1},{"version":"df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","impliedFormat":1},{"version":"4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","impliedFormat":1},{"version":"006d8ff9a051d61b0887b594b1e76c73314bb1a6fe39026867418937ea2259b3","impliedFormat":1},{"version":"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"5e76305d58bcdc924ff2bf14f6a9dc2aa5441ed06464b7e7bd039e611d66a89b","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"4371055bb001f40596e2e236b27583e13bf11e75d937962f8947d56519237fb8","impliedFormat":1},{"version":"d58caa8f60cc81409121aeaf3e9bfd6306fd3371026eaf94c6ab5a8e98086e4e","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[[162,170]],"options":{"allowImportingTsExtensions":true,"composite":true,"declaration":true,"declarationMap":true,"downlevelIteration":true,"module":99,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"target":9},"referencedMap":[[150,1],[158,2],[155,3],[149,4],[146,5],[156,6],[159,7],[160,8],[135,9],[152,10],[151,4],[148,4],[157,5],[147,11],[154,12],[171,5],[344,13],[346,14],[348,15],[349,16],[347,17],[350,18],[351,19],[352,20],[353,21],[354,22],[355,23],[356,24],[357,25],[358,26],[359,27],[228,28],[229,28],[230,29],[177,30],[231,31],[232,32],[233,33],[175,5],[234,34],[235,35],[236,36],[237,37],[238,38],[239,39],[240,39],[241,40],[242,41],[243,42],[244,43],[178,5],[176,5],[245,44],[246,45],[247,46],[281,47],[248,48],[249,5],[250,49],[251,50],[252,51],[253,52],[254,53],[255,54],[256,55],[257,56],[258,57],[259,57],[260,58],[261,5],[262,59],[263,60],[265,61],[264,62],[266,63],[267,64],[268,65],[269,66],[270,67],[271,68],[272,69],[273,70],[274,71],[275,72],[276,73],[277,74],[278,75],[179,5],[180,76],[181,5],[182,5],[224,77],[225,78],[226,5],[227,63],[279,79],[280,80],[360,81],[364,82],[361,5],[363,83],[389,84],[390,85],[366,86],[369,87],[387,84],[388,84],[378,84],[377,88],[375,84],[370,84],[383,84],[381,84],[385,84],[365,84],[382,84],[386,84],[371,84],[372,84],[384,84],[367,84],[373,84],[374,84],[376,84],[380,84],[391,89],[379,84],[368,84],[404,90],[403,5],[398,89],[400,91],[399,89],[392,89],[393,89],[395,89],[397,89],[401,91],[402,91],[394,91],[396,91],[406,92],[405,93],[407,5],[408,94],[172,5],[320,95],[342,5],[341,5],[335,96],[322,97],[321,5],[319,98],[323,5],[317,99],[324,5],[343,100],[325,5],[334,101],[336,102],[318,103],[340,104],[338,105],[337,106],[339,107],[326,5],[332,108],[329,109],[331,110],[330,111],[328,112],[327,5],[333,113],[362,5],[153,5],[345,5],[63,5],[64,5],[12,5],[10,5],[11,5],[16,5],[15,5],[2,5],[17,5],[18,5],[19,5],[20,5],[21,5],[22,5],[23,5],[24,5],[3,5],[25,5],[26,5],[4,5],[27,5],[31,5],[28,5],[29,5],[30,5],[32,5],[33,5],[34,5],[5,5],[35,5],[36,5],[37,5],[38,5],[6,5],[42,5],[39,5],[40,5],[41,5],[43,5],[7,5],[44,5],[49,5],[50,5],[45,5],[46,5],[47,5],[48,5],[8,5],[54,5],[51,5],[52,5],[53,5],[55,5],[9,5],[56,5],[65,5],[57,5],[58,5],[60,5],[59,5],[1,5],[61,5],[62,5],[14,5],[13,5],[295,114],[305,115],[294,114],[315,116],[286,117],[285,118],[314,119],[308,120],[313,121],[288,122],[302,123],[287,124],[311,125],[283,126],[282,119],[312,127],[284,128],[289,129],[290,5],[293,129],[173,5],[316,130],[306,131],[297,132],[298,133],[300,134],[296,135],[299,136],[309,119],[291,137],[292,138],[301,139],[174,140],[304,131],[303,129],[307,5],[310,141],[200,142],[212,143],[198,144],[213,140],[222,145],[189,146],[190,147],[188,118],[221,119],[216,148],[220,149],[192,150],[209,151],[191,152],[219,153],[186,154],[187,148],[193,155],[194,5],[199,156],[197,155],[184,157],[223,158],[214,159],[203,160],[202,155],[204,161],[207,162],[201,163],[205,164],[217,119],[195,165],[196,166],[208,167],[185,140],[211,168],[210,155],[206,169],[215,5],[183,5],[218,170],[161,171],[70,172],[77,173],[72,5],[73,5],[71,174],[74,175],[66,5],[67,5],[78,171],[69,176],[75,5],[76,177],[68,178],[139,179],[142,180],[140,180],[136,179],[143,181],[144,182],[141,180],[137,183],[138,184],[131,185],[83,186],[85,187],[129,5],[84,188],[130,189],[134,190],[132,5],[86,186],[87,5],[128,191],[82,192],[79,5],[133,193],[80,194],[81,5],[145,195],[88,196],[89,196],[90,196],[91,196],[92,196],[93,196],[94,196],[95,196],[96,196],[97,196],[98,196],[100,196],[99,196],[101,196],[102,196],[103,196],[127,197],[104,196],[105,196],[106,196],[107,196],[108,196],[109,196],[110,196],[111,196],[112,196],[114,196],[113,196],[115,196],[116,196],[117,196],[118,196],[119,196],[120,196],[121,196],[122,196],[123,196],[124,196],[125,196],[126,196],[166,198],[162,199],[164,200],[163,201],[165,202],[167,203],[170,204],[168,205],[169,206]],"affectedFilesPendingEmit":[[166,49],[162,49],[164,49],[163,49],[165,49],[167,49],[170,49],[168,49],[169,49]],"emitSignatures":[162,163,164,165,166,167,168,169,170],"version":"5.9.3"} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09d0cd918..f654c9d53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,6 +140,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + ed2curve: + specifier: ^0.3.0 + version: 0.3.0 i18next: specifier: ^25.7.1 version: 25.7.3(typescript@5.9.3) @@ -182,6 +185,9 @@ importers: tw-animate-css: specifier: ^1.4.0 version: 1.4.0 + tweetnacl: + specifier: ^1.0.3 + version: 1.0.3 vaul: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -243,6 +249,9 @@ importers: '@types/bun': specifier: ^1.3.5 version: 1.3.5 + '@types/ed2curve': + specifier: ^0.2.4 + version: 0.2.4 '@types/lodash': specifier: ^4.17.21 version: 4.17.21 @@ -303,6 +312,9 @@ importers: jsdom: specifier: ^27.2.0 version: 27.3.0 + jszip: + specifier: ^3.10.1 + version: 3.10.1 oxlint: specifier: ^1.39.0 version: 1.39.0 @@ -330,6 +342,9 @@ importers: sharp: specifier: ^0.34.5 version: 0.34.5 + sirv: + specifier: ^3.0.2 + version: 3.0.2 ssh2-sftp-client: specifier: ^12.0.1 version: 12.0.1 @@ -709,6 +724,15 @@ importers: specifier: ^5.9.3 version: 5.9.3 + packages/dweb-compat: + devDependencies: + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.0 + version: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) + packages/e2e-tools: devDependencies: typescript: @@ -3724,6 +3748,9 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/ed2curve@0.2.4': + resolution: {integrity: sha512-1m9IX8qypOa0K1NfdMsMxB3gjhKyXbr5Yl9FzzwWQjLSDFGLkvRvSfla1NYqzIt72ocIALMXsF+twRlzr1ov/g==} + '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -4880,6 +4907,9 @@ packages: resolution: {integrity: sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} + ed2curve@0.3.0: + resolution: {integrity: sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -5511,6 +5541,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -5825,6 +5858,9 @@ packages: jsqr@1.4.0: resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keccak@3.0.4: resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} engines: {node: '>=10.0.0'} @@ -5850,6 +5886,9 @@ packages: libphonenumber-js@1.12.33: resolution: {integrity: sha512-r9kw4OA6oDO4dPXkOrXTkArQAafIKAU71hChInV4FxZ69dxCfbwQGDPzqR5/vea94wU705/3AZroEbSoeVWrQw==} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -6369,6 +6408,9 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -7484,6 +7526,9 @@ packages: tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -11023,7 +11068,7 @@ snapshots: '@vitest/browser': 4.0.16(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.16) '@vitest/browser-playwright': 4.0.16(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3))(playwright@1.57.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.16) '@vitest/runner': 4.0.16 - vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) + vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) transitivePeerDependencies: - react - react-dom @@ -11337,6 +11382,10 @@ snapshots: '@types/doctrine@0.0.9': {} + '@types/ed2curve@0.2.4': + dependencies: + tweetnacl: 1.0.3 + '@types/eslint@9.6.1': dependencies: '@types/estree': 1.0.8 @@ -11543,7 +11592,7 @@ snapshots: '@vitest/mocker': 4.0.16(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)) playwright: 1.57.0 tinyrainbow: 3.0.3 - vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) + vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) transitivePeerDependencies: - bufferutil - msw @@ -11559,7 +11608,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) + vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(lightningcss@1.30.2)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3)) ws: 8.18.3 transitivePeerDependencies: - bufferutil @@ -12593,6 +12642,10 @@ snapshots: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 + ed2curve@0.3.0: + dependencies: + tweetnacl: 1.0.3 + ee-first@1.1.1: {} electron-to-chromium@1.5.267: {} @@ -13407,6 +13460,8 @@ snapshots: ignore@5.3.2: {} + immediate@3.0.6: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -13694,6 +13749,13 @@ snapshots: jsqr@1.4.0: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + keccak@3.0.4: dependencies: node-addon-api: 2.0.2 @@ -13717,6 +13779,10 @@ snapshots: libphonenumber-js@1.12.33: {} + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lightningcss-android-arm64@1.30.2: optional: true @@ -14213,6 +14279,8 @@ snapshots: package-manager-detector@1.6.0: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -15365,6 +15433,8 @@ snapshots: tweetnacl@0.14.5: {} + tweetnacl@1.0.3: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/scripts/i18n-check.ts b/scripts/i18n-check.ts index e44cc135e..8d7f0139a 100644 --- a/scripts/i18n-check.ts +++ b/scripts/i18n-check.ts @@ -8,6 +8,7 @@ * - Checks all namespaces across all locales * - Reports missing keys per locale * - Reports extra keys (keys only in one locale) + * - Checks if namespaces are registered in i18n/index.ts * - Supports --fix to add missing keys with placeholder values * - Exit code 1 if issues found (for CI) * @@ -24,6 +25,7 @@ import { resolve, join } from 'node:path' const ROOT = resolve(import.meta.dirname, '..') const LOCALES_DIR = join(ROOT, 'src/i18n/locales') +const I18N_INDEX_PATH = join(ROOT, 'src/i18n/index.ts') // Reference locale (source of truth for keys) const REFERENCE_LOCALE = 'zh-CN' @@ -190,6 +192,28 @@ function sortObjectKeys(obj: TranslationFile): TranslationFile { // ==================== Main Logic ==================== +/** + * Get registered namespaces from src/i18n/index.ts + */ +function getRegisteredNamespaces(): string[] { + const content = readFileSync(I18N_INDEX_PATH, 'utf-8') + // Match: export const namespaces = [...] as const + const match = content.match(/export\s+const\s+namespaces\s*=\s*\[([\s\S]*?)\]\s*as\s+const/) + if (!match) { + log.warn('Could not parse namespaces from src/i18n/index.ts') + return [] + } + // Extract string literals from the array + const arrayContent = match[1] + const namespaces: string[] = [] + const regex = /'([^']+)'|"([^"]+)"/g + let m + while ((m = regex.exec(arrayContent)) !== null) { + namespaces.push(m[1] || m[2]) + } + return namespaces +} + function getNamespaces(): string[] { const refDir = join(LOCALES_DIR, REFERENCE_LOCALE) return readdirSync(refDir) @@ -288,6 +312,23 @@ ${colors.cyan}╔═════════════════════ const namespaces = getNamespaces() log.info(`Found ${namespaces.length} namespaces`) + // Check for unregistered namespaces + log.step('Checking namespace registration') + const registeredNamespaces = getRegisteredNamespaces() + const unregisteredNamespaces = namespaces.filter((ns) => !registeredNamespaces.includes(ns)) + + if (unregisteredNamespaces.length > 0) { + log.error(`Found ${unregisteredNamespaces.length} unregistered namespace(s) in src/i18n/index.ts:`) + for (const ns of unregisteredNamespaces) { + log.dim(`- ${ns}`) + } + console.log(`\n${colors.red}✗ These namespaces have JSON files but are NOT registered in src/i18n/index.ts${colors.reset}`) + log.info(`Add them to the 'namespaces' array and 'resources' object in src/i18n/index.ts`) + process.exit(1) + } else { + log.success(`All ${namespaces.length} namespaces are registered`) + } + const allResults: CheckResult[] = [] for (const namespace of namespaces) { diff --git a/scripts/utils/fetch-with-etag.ts b/scripts/utils/fetch-with-etag.ts new file mode 100644 index 000000000..fe1cf3ac9 --- /dev/null +++ b/scripts/utils/fetch-with-etag.ts @@ -0,0 +1,150 @@ +/** + * 基于 ETag 的缓存下载工具 + * + * 缓存路径: /tmp/fetch/{sha256(etag)}.bin + * 临时路径: /tmp/fetch/{sha256(etag)}.bin.tmp (下载中) + * + * 特性: + * - ETag 匹配时直接返回缓存 + * - 下载完成后原子重命名 + * - 支持断点续传场景的安全写入 + */ + +import { createHash } from 'node:crypto' +import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs' +import { join } from 'node:path' +import { tmpdir } from 'node:os' + +const CACHE_DIR = join(tmpdir(), 'fetch') + +/** + * 计算 SHA256 哈希 + */ +function sha256(data: string): string { + return createHash('sha256').update(data).digest('hex') +} + +/** + * 确保缓存目录存在 + */ +function ensureCacheDir(): void { + if (!existsSync(CACHE_DIR)) { + mkdirSync(CACHE_DIR, { recursive: true }) + } +} + +/** + * 获取缓存文件路径 + */ +function getCachePath(etag: string): string { + const hash = sha256(etag) + return join(CACHE_DIR, `${hash}.bin`) +} + +/** + * 获取临时文件路径 + */ +function getTempPath(etag: string): string { + const hash = sha256(etag) + return join(CACHE_DIR, `${hash}.bin.tmp`) +} + +/** + * 基于 ETag 的缓存下载 + * + * @param url 要下载的 URL + * @returns 文件内容的 Buffer + * + * 流程: + * 1. HEAD 请求获取 ETag + * 2. 计算 hash = sha256(etag) + * 3. 检查 /tmp/fetch/{hash}.bin 是否存在 + * - 存在 → 直接返回缓存内容 + * - 不存在 → 下载到 .tmp,完成后重命名为 .bin + */ +export async function fetchWithEtag(url: string): Promise { + ensureCacheDir() + + // 1. HEAD 请求获取 ETag + const headResponse = await fetch(url, { method: 'HEAD' }) + if (!headResponse.ok) { + throw new Error(`HEAD request failed: ${headResponse.status} ${headResponse.statusText}`) + } + + const etag = headResponse.headers.get('etag') || headResponse.headers.get('last-modified') || url + const cachePath = getCachePath(etag) + const tempPath = getTempPath(etag) + + // 2. 检查缓存 + if (existsSync(cachePath)) { + console.log(`[fetchWithEtag] Cache hit: ${url}`) + return readFileSync(cachePath) + } + + // 3. 下载文件 + console.log(`[fetchWithEtag] Downloading: ${url}`) + const response = await fetch(url) + if (!response.ok) { + throw new Error(`Download failed: ${response.status} ${response.statusText}`) + } + + const arrayBuffer = await response.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + + // 4. 写入临时文件 + try { + // 清理可能存在的旧临时文件 + if (existsSync(tempPath)) { + unlinkSync(tempPath) + } + writeFileSync(tempPath, buffer) + + // 5. 原子重命名 + renameSync(tempPath, cachePath) + console.log(`[fetchWithEtag] Cached: ${cachePath}`) + } catch (error) { + // 清理临时文件 + if (existsSync(tempPath)) { + try { + unlinkSync(tempPath) + } catch { + // ignore cleanup errors + } + } + throw error + } + + return buffer +} + +/** + * 清理指定 URL 的缓存 + */ +export async function clearCache(url: string): Promise { + const headResponse = await fetch(url, { method: 'HEAD' }) + if (!headResponse.ok) return + + const etag = headResponse.headers.get('etag') || headResponse.headers.get('last-modified') || url + const cachePath = getCachePath(etag) + + if (existsSync(cachePath)) { + unlinkSync(cachePath) + console.log(`[fetchWithEtag] Cleared cache: ${cachePath}`) + } +} + +/** + * 清理所有缓存 + */ +export function clearAllCache(): void { + if (existsSync(CACHE_DIR)) { + const { readdirSync, rmSync } = require('node:fs') + const files = readdirSync(CACHE_DIR) + for (const file of files) { + rmSync(join(CACHE_DIR, file)) + } + console.log(`[fetchWithEtag] Cleared all cache (${files.length} files)`) + } +} + +export default fetchWithEtag diff --git a/scripts/vite-plugin-miniapps.ts b/scripts/vite-plugin-miniapps.ts index 1e4faf5da..77c65a900 100644 --- a/scripts/vite-plugin-miniapps.ts +++ b/scripts/vite-plugin-miniapps.ts @@ -11,6 +11,7 @@ import { resolve, join } from 'node:path' import { readdirSync, existsSync, readFileSync, writeFileSync, mkdirSync, cpSync } from 'node:fs' import detectPort from 'detect-port' import https from 'node:https' +import { getRemoteMiniappsForEcosystem } from './vite-plugin-remote-miniapps' // ==================== Types ==================== @@ -55,13 +56,12 @@ interface MiniappServer { interface MiniappsPluginOptions { miniappsDir?: string - startPort?: number } // ==================== Plugin ==================== export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { - const { miniappsDir = 'miniapps', startPort = 5180 } = options + const { miniappsDir = 'miniapps' } = options let root: string let isBuild = false @@ -121,16 +121,17 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { // 等待所有 miniapp 启动后,fetch 各自的 /manifest.json 生成 ecosystem const generateEcosystem = async (): Promise => { - const apps = await Promise.all( + // 本地 miniapps + const localApps = await Promise.all( miniappServers.map(async (s) => { try { const manifest = await fetchManifest(s.port) return { ...manifest, dirName: s.dirName, - icon: `${s.baseUrl}/${manifest.icon}`, - url: `${s.baseUrl}/`, - screenshots: manifest.screenshots.map((sc) => `${s.baseUrl}/${sc}`), + icon: new URL(manifest.icon, s.baseUrl).href, + url: new URL('/', s.baseUrl).href, + screenshots: manifest.screenshots.map((sc) => new URL(sc, s.baseUrl).href), } } catch (e) { console.error(`[miniapps] Failed to fetch manifest for ${s.id}:`, e) @@ -139,12 +140,18 @@ export function miniappsPlugin(options: MiniappsPluginOptions = {}): Plugin { }), ) + // 远程 miniapps (从 vite-plugin-remote-miniapps 获取) + const remoteApps = getRemoteMiniappsForEcosystem() + return { name: 'Bio 官方生态', version: '1.0.0', updated: new Date().toISOString().split('T')[0], icon: '/logos/logo-256.webp', - apps: apps.filter((a): a is NonNullable => a !== null), + apps: [ + ...localApps.filter((a): a is NonNullable => a !== null), + ...remoteApps, + ], } } @@ -194,6 +201,13 @@ function scanMiniapps(miniappsPath: string): MiniappManifest[] { continue } + // 跳过远程 miniapps (没有 vite.config.ts 的是已构建的远程 miniapp) + const viteConfigPath = join(miniappsPath, entry.name, 'vite.config.ts') + if (!existsSync(viteConfigPath)) { + // 远程 miniapp,由 vite-plugin-remote-miniapps 处理 + continue + } + try { const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as MiniappManifest manifests.push({ ...manifest, dirName: entry.name }) diff --git a/scripts/vite-plugin-remote-miniapps.ts b/scripts/vite-plugin-remote-miniapps.ts new file mode 100644 index 000000000..6de36e730 --- /dev/null +++ b/scripts/vite-plugin-remote-miniapps.ts @@ -0,0 +1,339 @@ +/** + * Vite Plugin: Remote Miniapps + * + * 下载并 serve 远程 miniapps: + * - Dev 模式:下载 manifest + zip,解压,启动静态服务器 + * - Build 模式:下载 manifest + zip,解压,复制到 dist/miniapps/ + * + * 使用 fetchWithEtag 实现基于 ETag 的缓存 + */ + +import { type Plugin } from 'vite' +import { resolve, join } from 'node:path' +import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, rmSync } from 'node:fs' +import { createServer } from 'node:http' +import type JSZipType from 'jszip' +import { fetchWithEtag } from './utils/fetch-with-etag' + +// ==================== Types ==================== + +interface RemoteMiniappConfig { + /** 远程 metadata.json URL (包含 version, zipUrl, manifestUrl) */ + metadataUrl: string + /** 解压到 miniapps/ 下的目录名 */ + dirName: string +} + +interface RemoteMetadata { + id: string + name: string + version: string + zipUrl: string + manifestUrl: string + updatedAt: string +} + +interface RemoteMiniappsPluginOptions { + /** 远程 miniapp 配置列表 */ + miniapps: RemoteMiniappConfig[] + /** miniapps 目录 (相对于项目根目录) */ + miniappsDir?: string +} + +interface MiniappManifest { + id: string + dirName: string + name: string + description: string + longDescription?: string + icon: string + version: string + author: string + website?: string + category: 'tools' | 'exchange' | 'social' | 'games' | 'other' + tags: string[] + permissions: string[] + chains: string[] + screenshots: string[] + publishedAt: string + updatedAt: string + beta: boolean + themeColor: string + officialScore?: number + communityScore?: number +} + +interface RemoteMiniappServer { + id: string + dirName: string + port: number + server: ReturnType + baseUrl: string + manifest: MiniappManifest +} + +// ==================== Plugin ==================== + +export function remoteMiniappsPlugin(options: RemoteMiniappsPluginOptions): Plugin { + const { miniapps, miniappsDir = 'miniapps' } = options + + let root: string + let isBuild = false + const servers: RemoteMiniappServer[] = [] + + return { + name: 'vite-plugin-remote-miniapps', + + configResolved(config) { + root = config.root + isBuild = config.command === 'build' + }, + + async buildStart() { + if (miniapps.length === 0) return + + const miniappsPath = resolve(root, miniappsDir) + + // 下载并解压所有远程 miniapps + for (const config of miniapps) { + await downloadAndExtract(config, miniappsPath) + } + }, + + async writeBundle(outputOptions) { + if (!isBuild || !outputOptions.dir) return + + const miniappsPath = resolve(root, miniappsDir) + const miniappsOutputDir = resolve(outputOptions.dir, 'miniapps') + + // 复制远程 miniapps 到 dist + for (const config of miniapps) { + const srcDir = join(miniappsPath, config.dirName) + const destDir = join(miniappsOutputDir, config.dirName) + + if (existsSync(srcDir)) { + mkdirSync(destDir, { recursive: true }) + cpSync(srcDir, destDir, { recursive: true }) + console.log(`[remote-miniapps] Copied ${config.dirName} to dist`) + } + } + }, + + async configureServer(server) { + if (miniapps.length === 0) return + + const miniappsPath = resolve(root, miniappsDir) + + // 先下载所有远程 miniapps (dev 模式下 buildStart 之后才执行 configureServer) + for (const config of miniapps) { + await downloadAndExtract(config, miniappsPath) + } + + // 启动静态服务器为每个远程 miniapp + for (const config of miniapps) { + const miniappDir = join(miniappsPath, config.dirName) + const manifestPath = join(miniappDir, 'manifest.json') + + if (!existsSync(manifestPath)) { + console.warn(`[remote-miniapps] ${config.dirName}: manifest.json not found, skipping`) + continue + } + + const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as MiniappManifest + + // 启动静态服务器 + const { server: httpServer, port } = await startStaticServer(miniappDir) + const baseUrl = `http://localhost:${port}` + + const serverInfo: RemoteMiniappServer = { + id: manifest.id, + dirName: config.dirName, + port, + server: httpServer, + baseUrl, + manifest, + } + + servers.push(serverInfo) + globalRemoteServers.push(serverInfo) + + console.log(`[remote-miniapps] ${manifest.name} (${manifest.id}) serving at ${baseUrl}`) + } + + // 清理服务器 + const cleanup = async () => { + for (const s of servers) { + await new Promise((resolve) => s.server.close(() => resolve())) + } + } + + server.httpServer?.on('close', cleanup) + }, + + async closeBundle() { + // 关闭所有静态服务器 + for (const s of servers) { + await new Promise((resolve) => s.server.close(() => resolve())) + } + }, + } +} + +// ==================== Helpers ==================== + +/** + * 下载并解压远程 miniapp + */ +async function downloadAndExtract( + config: RemoteMiniappConfig, + miniappsPath: string +): Promise { + const targetDir = join(miniappsPath, config.dirName) + + console.log(`[remote-miniapps] Syncing ${config.dirName}...`) + + // 1. 下载 metadata.json (获取最新版本信息) + // fetchWithEtag 会根据 ETag 决定是否使用缓存 + const metadataBuffer = await fetchWithEtag(config.metadataUrl) + const metadata = JSON.parse(metadataBuffer.toString('utf-8')) as RemoteMetadata + + // 检查本地是否已有相同版本且 zip 的 ETag 没变 + // 通过比较本地 manifest 中存储的 zipEtag 来判断 + const localManifestPath = join(targetDir, 'manifest.json') + if (existsSync(localManifestPath)) { + const localManifest = JSON.parse(readFileSync(localManifestPath, 'utf-8')) as MiniappManifest & { _zipEtag?: string } + if (localManifest.version === metadata.version && localManifest._zipEtag) { + // 检查远程 zip 的 ETag 是否变化 + const baseUrl = config.metadataUrl.replace(/\/[^/]+$/, '') + const zipUrl = metadata.zipUrl.startsWith('.') + ? `${baseUrl}/${metadata.zipUrl.slice(2)}` + : metadata.zipUrl + try { + const headResponse = await fetch(zipUrl, { method: 'HEAD' }) + const remoteEtag = headResponse.headers.get('etag') || '' + if (remoteEtag === localManifest._zipEtag) { + console.log(`[remote-miniapps] ${config.dirName} is up-to-date (v${metadata.version}, etag match)`) + return + } + console.log(`[remote-miniapps] ${config.dirName} zip changed (etag: ${localManifest._zipEtag} -> ${remoteEtag})`) + } catch { + // HEAD 请求失败,继续下载 + } + } + } + + // 2. 解析相对 URL + const baseUrl = config.metadataUrl.replace(/\/[^/]+$/, '') + const manifestUrl = metadata.manifestUrl.startsWith('.') + ? `${baseUrl}/${metadata.manifestUrl.slice(2)}` + : metadata.manifestUrl + const zipUrl = metadata.zipUrl.startsWith('.') + ? `${baseUrl}/${metadata.zipUrl.slice(2)}` + : metadata.zipUrl + + // 3. 下载 manifest.json + const manifestBuffer = await fetchWithEtag(manifestUrl) + const manifest = JSON.parse(manifestBuffer.toString('utf-8')) as MiniappManifest + + // 4. 下载 zip 并获取 ETag + const zipHeadResponse = await fetch(zipUrl, { method: 'HEAD' }) + const zipEtag = zipHeadResponse.headers.get('etag') || '' + const zipBuffer = await fetchWithEtag(zipUrl) + + // 5. 清理旧目录 + if (existsSync(targetDir)) { + rmSync(targetDir, { recursive: true }) + } + mkdirSync(targetDir, { recursive: true }) + + // 6. 解压 zip + const JSZip = (await import('jszip')).default + const zip = await JSZip.loadAsync(zipBuffer) + for (const [relativePath, file] of Object.entries(zip.files) as [ + string, + JSZipType.JSZipObject, + ][]) { + if (file.dir) { + mkdirSync(join(targetDir, relativePath), { recursive: true }) + } else { + const content = await file.async('nodebuffer') + const filePath = join(targetDir, relativePath) + mkdirSync(join(targetDir, relativePath, '..'), { recursive: true }) + writeFileSync(filePath, content) + } + } + + // 7. 写入 manifest.json (确保包含 dirName 和 _zipEtag 用于增量更新检测) + const manifestWithDir = { ...manifest, dirName: config.dirName, _zipEtag: zipEtag } + writeFileSync(localManifestPath, JSON.stringify(manifestWithDir, null, 2)) + + console.log(`[remote-miniapps] ${config.dirName} updated to v${manifest.version} (etag: ${zipEtag})`) +} + +/** + * 启动简单的静态文件服务器 + */ +async function startStaticServer( + root: string +): Promise<{ server: ReturnType; port: number }> { + const sirv = (await import('sirv')).default + const handler = sirv(root, { dev: true, single: true }) + + return new Promise((resolve, reject) => { + const server = createServer((req, res) => { + // CORS headers + res.setHeader('Access-Control-Allow-Origin', '*') + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS') + res.setHeader('Access-Control-Allow-Headers', 'Content-Type') + + if (req.method === 'OPTIONS') { + res.writeHead(204) + res.end() + return + } + + handler(req, res, () => { + res.writeHead(404) + res.end('Not found') + }) + }) + + server.listen(0, () => { + const address = server.address() + if (address && typeof address === 'object') { + resolve({ server, port: address.port }) + } else { + reject(new Error('Failed to get server address')) + } + }) + + server.on('error', reject) + }) +} + +// ==================== 共享状态 ==================== + +/** 全局注册的远程 miniapp 服务器 */ +const globalRemoteServers: RemoteMiniappServer[] = [] + +/** + * 获取远程 miniapps 的服务器信息 (供 ecosystem.json 生成使用) + */ +export function getRemoteMiniappServers(): RemoteMiniappServer[] { + return [...globalRemoteServers] +} + +/** + * 获取远程 miniapps 用于 ecosystem.json 的数据 + */ +export function getRemoteMiniappsForEcosystem(): Array { + return globalRemoteServers.map((s) => ({ + ...s.manifest, + dirName: s.dirName, + icon: new URL(s.manifest.icon, s.baseUrl).href, + url: new URL('/', s.baseUrl).href, + screenshots: s.manifest.screenshots?.map((sc) => new URL(sc, s.baseUrl).href) ?? [], + })) +} + +export default remoteMiniappsPlugin diff --git a/src/components/ecosystem/discover-page.tsx b/src/components/ecosystem/discover-page.tsx index 23c2d4f2c..71e08cfe2 100644 --- a/src/components/ecosystem/discover-page.tsx +++ b/src/components/ecosystem/discover-page.tsx @@ -62,7 +62,7 @@ function FeaturedStoryCard({ app, onTap }: { app: MiniappManifest; onTap: () =>
- {t('ecosystem:discover.featured')} + {t('discover.featured')}
@@ -166,7 +166,7 @@ function AppListItem({ onOpen(); }} > - {t('ecosystem:discover.get')} + {t('discover.get')}
); @@ -230,7 +230,7 @@ export const DiscoverPage = forwardRef(funct (funct {/* 内容区 */}
{apps.length === 0 ? ( - + ) : ( <> {featuredApp && ( @@ -256,9 +256,9 @@ export const DiscoverPage = forwardRef(funct {recommendedApps.length > 0 && (
-

{t('ecosystem:discover.recommended')}

+

{t('discover.recommended')}

@@ -277,7 +277,7 @@ export const DiscoverPage = forwardRef(funct )}
-

{t('ecosystem:discover.hotApps')}

+

{t('discover.hotApps')}

{hotApps.map((app, i) => (
diff --git a/src/components/ecosystem/miniapp-sheet-header.tsx b/src/components/ecosystem/miniapp-sheet-header.tsx index 65cff19a1..f8ef4081f 100644 --- a/src/components/ecosystem/miniapp-sheet-header.tsx +++ b/src/components/ecosystem/miniapp-sheet-header.tsx @@ -2,9 +2,24 @@ * MiniappSheetHeader - 统一的小程序 Sheet 页头组件 * * 用于 WalletPickerJob、ChainSwitchConfirmJob、SigningConfirmJob 等场景 + * + * 布局规则: + * - 有 walletInfo 时:左右布局(miniapp 左侧,wallet 右侧) + * - 无 walletInfo 时:miniapp 信息居中 */ import { MiniappIcon } from './miniapp-icon' +import { ChainBadge } from '@/components/wallet/chain-icon' + +/** 钱包信息(右侧显示) */ +export type WalletInfo = { + /** 钱包名称 */ + name: string + /** 钱包地址 */ + address: string + /** 链 ID */ + chainId: string +} export type MiniappSheetHeaderProps = { /** Sheet 标题 */ @@ -15,16 +30,62 @@ export type MiniappSheetHeaderProps = { appName?: string /** 来源小程序图标 URL */ appIcon?: string + /** 请求的链 ID (可选,传入时显示 ChainBadge,当有 walletInfo 时会被忽略) */ + chainId?: string + /** 钱包信息 (可选,传入时在右侧显示钱包名+地址+链) */ + walletInfo?: WalletInfo } -export function MiniappSheetHeader({ title, description, appName, appIcon }: MiniappSheetHeaderProps) { +/** 格式化地址为短格式 */ +function formatAddress(address: string): string { + if (address.length <= 14) return address + return `${address.slice(0, 6)}...${address.slice(-4)}` +} + +export function MiniappSheetHeader({ title, description, appName, appIcon, chainId, walletInfo }: MiniappSheetHeaderProps) { + // 有钱包信息时使用左右布局 + if (walletInfo) { + return ( +
+
+ {/* 左侧:Miniapp 信息 */} +
+ +
+

{title}

+ {description &&

{description}

} +
+
+ + {/* 右侧:钱包信息 */} +
+
+

{walletInfo.name}

+

{formatAddress(walletInfo.address)}

+
+ +
+
+
+ ) + } + + // 无钱包信息时使用原有布局 return ( -
-
- +
+
+ {/* 左侧:App 图标 */} + + + {/* 中间:标题和描述 */} +
+

{title}

+ {description &&

{description}

} +
+ + {/* 右侧:ChainBadge (可选) */} + {chainId && }
-

{title}

- {description &&

{description}

}
) } diff --git a/src/components/security/crypto-authorize.tsx b/src/components/security/crypto-authorize.tsx new file mode 100644 index 000000000..725bb3371 --- /dev/null +++ b/src/components/security/crypto-authorize.tsx @@ -0,0 +1,209 @@ +/** + * Crypto 授权对话框组件 + * + * 显示授权请求信息,让用户输入手势密码确认授权 + */ + +import { useState, useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { PatternLock, patternToString } from './pattern-lock' +import { walletStorageService } from '@/services/wallet-storage' +import { + type CryptoAction, + type TokenDuration, + CRYPTO_ACTION_LABELS, + TOKEN_DURATION_LABELS, + TOKEN_DURATION_OPTIONS, +} from '@/services/crypto-box' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' + +export interface CryptoAuthorizeProps { + /** 请求的操作权限 */ + actions: CryptoAction[] + /** 默认授权时长 */ + duration: TokenDuration + /** 使用的地址 */ + address: string + /** 请求来源应用 */ + app: { + name: string + icon?: string + } + /** 确认回调,返回选择的时长 */ + onConfirm: (patternKey: string, selectedDuration: TokenDuration) => void + /** 取消回调 */ + onCancel: () => void +} + +/** + * Crypto 授权对话框 + */ +export function CryptoAuthorize({ + actions, + duration, + address, + app, + onConfirm, + onCancel, +}: CryptoAuthorizeProps) { + const { t } = useTranslation() + const [pattern, setPattern] = useState([]) + const [error, setError] = useState(false) + const [verifying, setVerifying] = useState(false) + const [selectedDuration, setSelectedDuration] = useState(duration) + + const handlePatternComplete = useCallback( + async (nodes: number[]) => { + setVerifying(true) + setError(false) + + try { + const patternKey = patternToString(nodes) + + // 验证手势密码是否正确(尝试解密任意钱包的 mnemonic) + const wallets = await walletStorageService.getAllWallets() + if (wallets.length === 0) { + setError(true) + setPattern([]) + return + } + + let isValid = false + for (const wallet of wallets) { + try { + await walletStorageService.getMnemonic(wallet.id, patternKey) + isValid = true + break + } catch { + // 继续尝试下一个钱包 + } + } + + if (isValid) { + onConfirm(patternKey, selectedDuration) + } else { + setError(true) + setPattern([]) + } + } catch { + setError(true) + setPattern([]) + } finally { + setVerifying(false) + } + }, + [onConfirm, selectedDuration] + ) + + return ( +
+
+ {/* Header */} +
+
+ {app.icon && ( + + )} +

{app.name}

+
+

+ {t('crypto.authorize.title', { defaultValue: '请求加密授权' })} +

+
+ + {/* 请求的权限 */} +
+
+ {t('crypto.authorize.permissions', { defaultValue: '请求权限' })} +
+
    + {actions.map((action) => ( +
  • + +
    +
    {CRYPTO_ACTION_LABELS[action].name}
    +
    + {CRYPTO_ACTION_LABELS[action].description} +
    +
    +
  • + ))} +
+
+ + {/* 授权时长和地址 */} +
+
+ + {t('crypto.authorize.duration', { defaultValue: '授权时长' })} + + +
+
+ + {t('crypto.authorize.address', { defaultValue: '使用地址' })} + + + {address.slice(0, 8)}...{address.slice(-6)} + +
+
+ + {/* 手势密码 */} +
+
+ {t('crypto.authorize.pattern', { defaultValue: '请输入手势密码确认' })} +
+
+ +
+ {error && ( +

+ {t('crypto.authorize.error', { defaultValue: '手势密码错误,请重试' })} +

+ )} +
+ + {/* 取消按钮 */} + +
+
+ ) +} diff --git a/src/components/wallet/chain-icon.test.tsx b/src/components/wallet/chain-icon.test.tsx index 5f729bd6b..831d0cfef 100644 --- a/src/components/wallet/chain-icon.test.tsx +++ b/src/components/wallet/chain-icon.test.tsx @@ -1,32 +1,70 @@ -import { describe, it, expect } from 'vitest' +import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen } from '@testing-library/react' import { ChainIcon, ChainBadge } from './chain-icon' +// Mock chainConfigService +vi.mock('@/services/chain-config', () => ({ + chainConfigService: { + getIcon: vi.fn((chainId: string) => { + // 返回 null 以测试 fallback 行为 + return null + }), + getSymbol: vi.fn((chainId: string) => { + const symbols: Record = { + ethereum: 'ETH', + tron: 'TRX', + bsc: 'BNB', + bitcoin: 'BTC', + bfmeta: 'BFM', + ccchain: 'CCC', + pmchain: 'PMC', + } + return symbols[chainId] ?? chainId.slice(0, 4).toUpperCase() + }), + getName: vi.fn((chainId: string) => { + const names: Record = { + ethereum: 'Ethereum', + tron: 'Tron', + bsc: 'BSC', + bitcoin: 'Bitcoin', + bfmeta: 'BFMeta', + ccchain: 'CCChain', + pmchain: 'PMChain', + } + return names[chainId] ?? chainId + }), + }, +})) + describe('ChainIcon', () => { - it('renders with correct aria-label', () => { - render() + beforeEach(() => { + vi.clearAllMocks() + }) + + it('renders with correct aria-label from symbol', () => { + render() expect(screen.getByLabelText('ETH')).toBeInTheDocument() }) - it('displays first letter of chain label', () => { - render() + it('displays first letter of symbol as fallback', () => { + render() expect(screen.getByText('E')).toBeInTheDocument() }) it('applies correct size class', () => { - const { rerender } = render() + const { rerender } = render() expect(screen.getByLabelText('ETH')).toHaveClass('size-5') - rerender() + rerender() expect(screen.getByLabelText('ETH')).toHaveClass('size-10') }) - it('applies chain-specific color', () => { - render() - expect(screen.getByLabelText('ETH')).toHaveClass('bg-chain-ethereum') + it('applies fallback background color when no icon', () => { + render() + expect(screen.getByLabelText('ETH')).toHaveClass('bg-muted') }) - it('renders all supported chains', () => { + it('renders all supported chains with correct symbols', () => { const chains = ['ethereum', 'tron', 'bsc', 'bitcoin', 'bfmeta', 'ccchain', 'pmchain'] as const const labels = { ethereum: 'ETH', @@ -38,26 +76,60 @@ describe('ChainIcon', () => { pmchain: 'PMC', } as const chains.forEach((chain) => { - const { unmount } = render() + const { unmount } = render() expect(screen.getByLabelText(labels[chain])).toBeInTheDocument() unmount() }) }) + + it('uses iconUrl prop when provided', () => { + render() + const img = screen.getByRole('img') + expect(img).toHaveAttribute('src', '/test-icon.png') + }) + + it('uses symbol prop when provided', () => { + render() + expect(screen.getByLabelText('CUSTOM')).toBeInTheDocument() + }) + + it('supports legacy chain prop', () => { + render() + expect(screen.getByLabelText('ETH')).toBeInTheDocument() + }) }) describe('ChainBadge', () => { - it('renders chain icon and label', () => { - render() - expect(screen.getByText('ETH')).toBeInTheDocument() - expect(screen.getByLabelText('ETH')).toBeInTheDocument() + beforeEach(() => { + vi.clearAllMocks() + }) + + it('renders chain icon and name', () => { + render() + expect(screen.getByText('Ethereum')).toBeInTheDocument() }) - it('renders all chain badges', () => { + it('renders all chain badges with correct names', () => { const chains = ['ethereum', 'tron', 'bitcoin'] as const + const names = { + ethereum: 'Ethereum', + tron: 'Tron', + bitcoin: 'Bitcoin', + } as const chains.forEach((chain) => { - const { unmount } = render() - expect(screen.getByLabelText(chain === 'ethereum' ? 'ETH' : chain === 'tron' ? 'TRX' : 'BTC')).toBeInTheDocument() + const { unmount } = render() + expect(screen.getByText(names[chain])).toBeInTheDocument() unmount() }) }) + + it('uses symbol prop for label when provided', () => { + render() + expect(screen.getByText('CUSTOM')).toBeInTheDocument() + }) + + it('supports legacy chain prop', () => { + render() + expect(screen.getByText('Ethereum')).toBeInTheDocument() + }) }) diff --git a/src/components/wallet/chain-icon.tsx b/src/components/wallet/chain-icon.tsx index 3e8edf194..cfedc8960 100644 --- a/src/components/wallet/chain-icon.tsx +++ b/src/components/wallet/chain-icon.tsx @@ -1,51 +1,13 @@ -import { useState, createContext, useContext, type ReactNode } from 'react'; +import { useState } from 'react'; import { cn } from '@/lib/utils'; +import { chainConfigService } from '@/services/chain-config'; export type ChainType = string; -/** - * ChainIcon Context - 提供 chainId -> iconUrl 的映射 - */ -interface ChainIconContextValue { - getIconUrl: (chainId: string) => string | undefined; -} - -const ChainIconContext = createContext(null); - -interface ChainIconProviderProps { - /** 获取链图标 URL 的函数 */ - getIconUrl: (chainId: string) => string | undefined; - children: ReactNode; -} - -/** - * ChainIcon Provider - * - * 在应用根部注入,提供 chainId -> iconUrl 的自动解析 - * - * @example - * const { configs } = useChainConfig(); - * - * configs.find(c => c.id === id)?.icon}> - * - * - */ -export function ChainIconProvider({ getIconUrl, children }: ChainIconProviderProps) { - return ( - - {children} - - ); -} - -function useChainIconContext() { - return useContext(ChainIconContext); -} - interface ChainIconProps { - /** 链 ID,用于 fallback 显示和从 context 获取 iconUrl */ + /** 链 ID,用于从 chainConfigService 获取图标和符号 */ chainId?: ChainType | undefined; - /** 图标 URL(优先使用,覆盖 context) */ + /** 图标 URL(优先使用,覆盖 service) */ iconUrl?: string | undefined; /** 链符号,用于 fallback 显示 */ symbol?: string | undefined; @@ -55,40 +17,6 @@ interface ChainIconProps { chain?: ChainType | undefined; } -const chainColors: Record = { - ethereum: 'bg-chain-ethereum', - tron: 'bg-chain-tron', - bitcoin: 'bg-chain-bitcoin', - binance: 'bg-chain-binance', - bsc: 'bg-chain-binance', - bfmeta: 'bg-chain-bfmeta', - ccchain: 'bg-emerald-500', - pmchain: 'bg-violet-500', - bfchainv2: 'bg-chain-bfmeta', - btgmeta: 'bg-amber-500', - biwmeta: 'bg-cyan-500', - ethmeta: 'bg-indigo-500', - malibu: 'bg-pink-500', - ccc: 'bg-emerald-500', -}; - -const chainLabels: Record = { - ethereum: 'ETH', - tron: 'TRX', - bitcoin: 'BTC', - binance: 'BNB', - bsc: 'BNB', - bfmeta: 'BFM', - ccchain: 'CCC', - pmchain: 'PMC', - bfchainv2: 'BFT', - btgmeta: 'BTGM', - biwmeta: 'BIW', - ethmeta: 'ETHM', - malibu: 'MLB', - ccc: 'CCC', -}; - const sizeClasses = { sm: 'size-5', md: 'size-8', @@ -101,38 +29,30 @@ const textSizeClasses = { lg: 'text-sm', }; -function toFallbackLabel(chainId?: string, symbol?: string): string { - if (symbol) return symbol.slice(0, 4).toUpperCase(); - if (chainId) { - const label = chainLabels[chainId]; - if (label) return label; - return chainId.slice(0, 4).toUpperCase(); - } - return '?'; -} - /** * 链图标组件 * - * 优先级:iconUrl prop > context > 首字母 + 背景色 + * 优先级:iconUrl prop > chainConfigService > 首字母 fallback * * @example - * // 自动从 context 获取图标 + * // 自动从 chainConfigService 获取图标 * * - * // 手动指定图标 URL(覆盖 context) + * // 手动指定图标 URL * */ export function ChainIcon({ chainId, iconUrl, symbol, size = 'md', className, chain }: ChainIconProps) { const [imgError, setImgError] = useState(false); - const context = useChainIconContext(); // 兼容旧的 chain prop const resolvedChainId = chainId ?? chain; - const label = toFallbackLabel(resolvedChainId, symbol); - // 解析图标 URL - const resolvedIconUrl = iconUrl ?? (resolvedChainId ? context?.getIconUrl(resolvedChainId) : undefined); + // 从 service 获取图标和符号 + const serviceIcon = resolvedChainId ? chainConfigService.getIcon(resolvedChainId) : null; + const serviceSymbol = resolvedChainId ? chainConfigService.getSymbol(resolvedChainId) : ''; + + const resolvedIconUrl = iconUrl ?? serviceIcon; + const label = symbol ?? serviceSymbol ?? resolvedChainId?.slice(0, 4).toUpperCase() ?? '?'; // 有图标 URL 且未加载失败时,使用图片 if (resolvedIconUrl && !imgError) { @@ -147,15 +67,11 @@ export function ChainIcon({ chainId, iconUrl, symbol, size = 'md', className, ch } // Fallback: 首字母 + 背景色 - const bgColor = resolvedChainId ? chainColors[resolvedChainId] : undefined; - const isKnown = resolvedChainId ? resolvedChainId in chainColors : false; - return (
// 显示 BFMeta 图标和名称 + */ export function ChainBadge({ chainId, iconUrl, symbol, className, chain }: ChainBadgeProps) { const resolvedChainId = chainId ?? chain ?? ''; - const label = symbol ?? chainLabels[resolvedChainId] ?? resolvedChainId; + + // 使用 chainConfigService 获取完整链名 + const label = symbol ?? (resolvedChainId ? chainConfigService.getName(resolvedChainId) : resolvedChainId); + return ( - + {label} ); diff --git a/src/components/wallet/index.ts b/src/components/wallet/index.ts index 3611d0fd1..a2d7688dc 100644 --- a/src/components/wallet/index.ts +++ b/src/components/wallet/index.ts @@ -19,7 +19,7 @@ export interface WalletInfo { chainIconUrl?: string | undefined } export { AddressDisplay } from './address-display' -export { ChainIcon, ChainBadge, ChainIconProvider, type ChainType } from './chain-icon' +export { ChainIcon, ChainBadge, type ChainType } from './chain-icon' export { ChainAddressDisplay } from './chain-address-display' export { TokenIcon, TokenBadge, TokenIconProvider } from './token-icon' export { WalletAddressPortfolioView, type WalletAddressPortfolioViewProps } from './wallet-address-portfolio-view' diff --git a/src/frontend-main.tsx b/src/frontend-main.tsx index 361455d87..d8a7418fe 100644 --- a/src/frontend-main.tsx +++ b/src/frontend-main.tsx @@ -8,7 +8,7 @@ import { resolveAssetUrl } from './lib/asset-url' import { ServiceProvider } from './services' import { MigrationProvider } from './contexts/MigrationContext' import { StackflowApp } from './StackflowApp' -import { ChainIconProvider, TokenIconProvider } from './components/wallet' +import { TokenIconProvider } from './components/wallet' import { useChainConfigs } from './stores/chain-config' import { AppInitializer } from './providers' import './styles/globals.css' @@ -31,34 +31,26 @@ const MockDevTools = __MOCK_MODE__ ? lazy(() => import('./services/mock-devtools').then((m) => ({ default: m.MockDevTools }))) : null -function IconProvidersWrapper({ children }: { children: React.ReactNode }) { +function TokenIconProviderWrapper({ children }: { children: React.ReactNode }) { const configs = useChainConfigs() // 预处理配置:解析所有相对路径为完整 URL const resolvedConfigs = useMemo(() => { return configs.map((config) => ({ ...config, - icon: config.icon ? resolveAssetUrl(config.icon) : undefined, tokenIconBase: config.tokenIconBase?.map(resolveAssetUrl), })) }, [configs]) - const getIconUrl = useCallback( - (chainId: string) => resolvedConfigs.find((c) => c.id === chainId)?.icon, - [resolvedConfigs], - ) - const getTokenIconBases = useCallback( (chainId: string) => resolvedConfigs.find((c) => c.id === chainId)?.tokenIconBase ?? [], [resolvedConfigs], ) return ( - - - {children} - - + + {children} + ) } @@ -70,9 +62,9 @@ export function startFrontendMain(rootElement: HTMLElement): void { - + - + {/* Mock DevTools - 仅在 mock 模式下显示 */} {MockDevTools && ( diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 922ad8dbc..8acd9d232 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -21,6 +21,7 @@ import enCurrency from './locales/en/currency.json' import enOnboarding from './locales/en/onboarding.json' import enNotification from './locales/en/notification.json' import enHome from './locales/en/home.json' +import enEcosystem from './locales/en/ecosystem.json' // Namespace imports - zh-CN import zhCNCommon from './locales/zh-CN/common.json' @@ -42,6 +43,7 @@ import zhCNCurrency from './locales/zh-CN/currency.json' import zhCNOnboarding from './locales/zh-CN/onboarding.json' import zhCNNotification from './locales/zh-CN/notification.json' import zhCNHome from './locales/zh-CN/home.json' +import zhCNEcosystem from './locales/zh-CN/ecosystem.json' // Namespace imports - zh-TW import zhTWCommon from './locales/zh-TW/common.json' @@ -63,6 +65,7 @@ import zhTWCurrency from './locales/zh-TW/currency.json' import zhTWOnboarding from './locales/zh-TW/onboarding.json' import zhTWNotification from './locales/zh-TW/notification.json' import zhTWHome from './locales/zh-TW/home.json' +import zhTWEcosystem from './locales/zh-TW/ecosystem.json' // Namespace imports - ar import arCommon from './locales/ar/common.json' @@ -84,6 +87,7 @@ import arCurrency from './locales/ar/currency.json' import arOnboarding from './locales/ar/onboarding.json' import arNotification from './locales/ar/notification.json' import arHome from './locales/ar/home.json' +import arEcosystem from './locales/ar/ecosystem.json' // 语言配置 export const languages = { @@ -118,6 +122,7 @@ export const namespaces = [ 'currency', 'notification', 'home', + 'ecosystem', ] as const export type Namespace = (typeof namespaces)[number] @@ -156,6 +161,7 @@ i18n.use(initReactI18next).init({ currency: enCurrency, notification: enNotification, home: enHome, + ecosystem: enEcosystem, }, 'zh-CN': { common: zhCNCommon, @@ -177,6 +183,7 @@ i18n.use(initReactI18next).init({ currency: zhCNCurrency, notification: zhCNNotification, home: zhCNHome, + ecosystem: zhCNEcosystem, }, 'zh-TW': { common: zhTWCommon, @@ -198,6 +205,7 @@ i18n.use(initReactI18next).init({ currency: zhTWCurrency, notification: zhTWNotification, home: zhTWHome, + ecosystem: zhTWEcosystem, }, ar: { common: arCommon, @@ -219,6 +227,7 @@ i18n.use(initReactI18next).init({ currency: arCurrency, notification: arNotification, home: arHome, + ecosystem: arEcosystem, }, }, lng: defaultLanguage, diff --git a/src/services/chain-config/service.ts b/src/services/chain-config/service.ts index 3bf550da2..e2b63c887 100644 --- a/src/services/chain-config/service.ts +++ b/src/services/chain-config/service.ts @@ -132,6 +132,22 @@ class ChainConfigService { if (!template) return null return template.replace(':address', address) } + + /** + * 获取链名称 + */ + getName(chainId: string): string { + const config = this.getConfig(chainId) + return config?.name ?? chainId + } + + /** + * 获取链图标 URL (已解析为完整 URL) + */ + getIcon(chainId: string): string | null { + const config = this.getConfig(chainId) + return config?.icon ?? null + } } export const chainConfigService = new ChainConfigService() diff --git a/src/services/crypto-box/__tests__/token-store.test.ts b/src/services/crypto-box/__tests__/token-store.test.ts new file mode 100644 index 000000000..54eb76c9a --- /dev/null +++ b/src/services/crypto-box/__tests__/token-store.test.ts @@ -0,0 +1,424 @@ +/** + * TokenStore 单元测试 + * + * 测试 Token 的 CRUD 和验证功能 + * + * 注意:新 API 返回 { token, sessionSecret },验证需要 sessionSecret + */ + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { tokenStore } from '../token-store' +import type { CryptoAction, TokenDuration } from '../types' + +// Mock IndexedDB +import 'fake-indexeddb/auto' + +describe('TokenStore', () => { + beforeEach(async () => { + // 重置 tokenStore 状态 + tokenStore.close() + // 初始化存储 + await tokenStore.initialize() + }) + + afterEach(() => { + // 关闭连接 + tokenStore.close() + }) + + describe('initialize', () => { + it('should initialize without error', async () => { + tokenStore.close() + await expect(tokenStore.initialize()).resolves.not.toThrow() + }) + + it('should be idempotent', async () => { + await tokenStore.initialize() + await tokenStore.initialize() + // Should not throw + }) + }) + + describe('generateTokenId', () => { + it('should generate a valid UUID', () => { + const tokenId = tokenStore.generateTokenId() + expect(tokenId).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + ) + }) + + it('should generate unique IDs', () => { + const id1 = tokenStore.generateTokenId() + const id2 = tokenStore.generateTokenId() + expect(id1).not.toBe(id2) + }) + }) + + describe('deriveSessionSecret', () => { + it('should generate SHA-256 hash', async () => { + const secret = await tokenStore.deriveSessionSecret( + 'wallet-1', + '0-1-2-5-8', + 'app.test.dweb', + 'token-123' + ) + expect(secret).toHaveLength(64) // 32 bytes = 64 hex chars + }) + + it('should generate consistent hash for same input', async () => { + const secret1 = await tokenStore.deriveSessionSecret( + 'wallet-1', + 'pattern', + 'app.test.dweb', + 'token-123' + ) + const secret2 = await tokenStore.deriveSessionSecret( + 'wallet-1', + 'pattern', + 'app.test.dweb', + 'token-123' + ) + expect(secret1).toBe(secret2) + }) + + it('should generate different hashes for different inputs', async () => { + const secret1 = await tokenStore.deriveSessionSecret( + 'wallet-1', + 'pattern-1', + 'app.test.dweb', + 'token-123' + ) + const secret2 = await tokenStore.deriveSessionSecret( + 'wallet-1', + 'pattern-2', + 'app.test.dweb', + 'token-123' + ) + expect(secret1).not.toBe(secret2) + }) + }) + + describe('createToken', () => { + it('should create a token with correct properties', async () => { + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1testaddress', + actions: ['asymmetricEncrypt'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: '0-1-2-5-8', + }) + + expect(token.tokenId).toBeDefined() + expect(token.miniappId).toBe('app.test.dweb') + expect(token.walletId).toBe('wallet-1') + expect(token.address).toBe('bfm1testaddress') + expect(token.actions).toContain('asymmetricEncrypt') + expect(token.createdAt).toBeLessThanOrEqual(Date.now()) + expect(token.expiresAt).toBeGreaterThan(Date.now()) + expect(sessionSecret).toHaveLength(64) + }) + + it('should set correct expiry for 5min duration', async () => { + const before = Date.now() + const { token } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '5min' as TokenDuration, + patternKey: 'pattern', + }) + const after = Date.now() + + const expectedExpiry = 5 * 60 * 1000 + expect(token.expiresAt).toBeGreaterThanOrEqual(before + expectedExpiry) + expect(token.expiresAt).toBeLessThanOrEqual(after + expectedExpiry) + }) + + it('should not include encryptedPayload in returned token', async () => { + const { token } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['asymmetricEncrypt'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'secret-pattern', + }) + + // CryptoToken should not include encryptedPayload + expect('encryptedPayload' in token).toBe(false) + }) + }) + + describe('getToken', () => { + it('should retrieve a stored token', async () => { + const { token: created } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['asymmetricEncrypt'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern', + }) + + const retrieved = await tokenStore.getToken(created.tokenId) + + expect(retrieved).not.toBeNull() + expect(retrieved!.tokenId).toBe(created.tokenId) + expect(retrieved!.encryptedPayload).toBeDefined() // StoredToken includes encryptedPayload + }) + + it('should return null for non-existent token', async () => { + const token = await tokenStore.getToken('non-existent-id') + expect(token).toBeNull() + }) + }) + + describe('deleteToken', () => { + it('should delete a token', async () => { + const { token } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern', + }) + + await tokenStore.deleteToken(token.tokenId) + const retrieved = await tokenStore.getToken(token.tokenId) + + expect(retrieved).toBeNull() + }) + + it('should not throw for non-existent token', async () => { + await expect(tokenStore.deleteToken('non-existent')).resolves.not.toThrow() + }) + }) + + describe('validateToken', () => { + it('should return valid for correct token, sessionSecret and action', async () => { + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['asymmetricEncrypt', 'sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern', + }) + + const result = await tokenStore.validateToken( + token.tokenId, + sessionSecret, + 'app.test.dweb', + 'asymmetricEncrypt' + ) + + expect(result.valid).toBe(true) + if (result.valid) { + expect(result.token.tokenId).toBe(token.tokenId) + expect(result.payload.patternKey).toBe('pattern') + } + }) + + it('should return TOKEN_NOT_FOUND for non-existent token', async () => { + const result = await tokenStore.validateToken( + 'non-existent', + 'any-secret', + 'app.test.dweb', + 'sign' + ) + + expect(result.valid).toBe(false) + if (!result.valid) { + expect(result.error).toBe('TOKEN_NOT_FOUND') + } + }) + + it('should return INVALID_SESSION_SECRET for wrong sessionSecret', async () => { + const { token } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern', + }) + + const result = await tokenStore.validateToken( + token.tokenId, + 'wrong-session-secret', + 'app.test.dweb', + 'sign' + ) + + expect(result.valid).toBe(false) + if (!result.valid) { + expect(result.error).toBe('INVALID_SESSION_SECRET') + } + }) + + it('should return MINIAPP_MISMATCH for wrong miniappId', async () => { + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: 'app.correct.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern', + }) + + const result = await tokenStore.validateToken( + token.tokenId, + sessionSecret, + 'app.wrong.dweb', + 'sign' + ) + + expect(result.valid).toBe(false) + if (!result.valid) { + expect(result.error).toBe('MINIAPP_MISMATCH') + } + }) + + it('should return TOKEN_EXPIRED for expired token', async () => { + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '5min' as TokenDuration, + patternKey: 'pattern', + }) + + // Mock Date.now to be in the future + const originalNow = Date.now + vi.spyOn(Date, 'now').mockReturnValue(token.expiresAt + 1000) + + const result = await tokenStore.validateToken( + token.tokenId, + sessionSecret, + 'app.test.dweb', + 'sign' + ) + + // Restore + Date.now = originalNow + + expect(result.valid).toBe(false) + if (!result.valid) { + expect(result.error).toBe('TOKEN_EXPIRED') + } + }) + + it('should return ACTION_NOT_PERMITTED for unauthorized action', async () => { + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['asymmetricEncrypt'] as CryptoAction[], // Only asymmetricEncrypt + duration: '30min' as TokenDuration, + patternKey: 'pattern', + }) + + const result = await tokenStore.validateToken( + token.tokenId, + sessionSecret, + 'app.test.dweb', + 'sign' // Request sign, but only asymmetricEncrypt is authorized + ) + + expect(result.valid).toBe(false) + if (!result.valid) { + expect(result.error).toBe('ACTION_NOT_PERMITTED') + } + }) + }) + + describe('getTokensByMiniapp', () => { + it('should return all tokens for a miniapp', async () => { + // Use unique miniappId to avoid pollution from other tests + const uniqueId = `app.getby.${Date.now()}.dweb` + const otherId = `app.other.${Date.now()}.dweb` + + // Create multiple tokens + await tokenStore.createToken({ + miniappId: uniqueId, + walletId: 'wallet-1', + address: 'bfm1test1', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern1', + }) + + await tokenStore.createToken({ + miniappId: uniqueId, + walletId: 'wallet-1', + address: 'bfm1test2', + actions: ['asymmetricEncrypt'] as CryptoAction[], + duration: '2hour' as TokenDuration, + patternKey: 'pattern2', + }) + + await tokenStore.createToken({ + miniappId: otherId, // Different miniapp + walletId: 'wallet-2', + address: 'bfm1other', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'pattern3', + }) + + const tokens = await tokenStore.getTokensByMiniapp(uniqueId) + + expect(tokens).toHaveLength(2) + tokens.forEach(token => { + expect(token.miniappId).toBe(uniqueId) + // Should not include encryptedPayload + expect('encryptedPayload' in token).toBe(false) + }) + }) + + it('should return empty array for miniapp with no tokens', async () => { + const tokens = await tokenStore.getTokensByMiniapp('app.notoken.dweb') + expect(tokens).toHaveLength(0) + }) + }) + + describe('decryptPayload', () => { + it('should decrypt payload with correct sessionSecret', async () => { + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'my-secret-pattern', + }) + + const storedToken = await tokenStore.getToken(token.tokenId) + expect(storedToken).not.toBeNull() + + const payload = await tokenStore.decryptPayload(storedToken!, sessionSecret) + expect(payload).not.toBeNull() + expect(payload!.patternKey).toBe('my-secret-pattern') + expect(payload!.miniappId).toBe('app.test.dweb') + }) + + it('should return null for wrong sessionSecret', async () => { + const { token } = await tokenStore.createToken({ + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'] as CryptoAction[], + duration: '30min' as TokenDuration, + patternKey: 'my-secret-pattern', + }) + + const storedToken = await tokenStore.getToken(token.tokenId) + const payload = await tokenStore.decryptPayload(storedToken!, 'wrong-secret') + expect(payload).toBeNull() + }) + }) +}) diff --git a/src/services/crypto-box/__tests__/types.test.ts b/src/services/crypto-box/__tests__/types.test.ts new file mode 100644 index 000000000..32eba60d6 --- /dev/null +++ b/src/services/crypto-box/__tests__/types.test.ts @@ -0,0 +1,148 @@ +/** + * CryptoBox 类型定义测试 + */ + +import { describe, it, expect } from 'vitest' +import { + TOKEN_DURATION_MS, + CryptoBoxErrorCodes, + type CryptoAction, + type TokenDuration, + type CryptoToken, + type StoredToken, + type RequestCryptoTokenParams, + type CryptoExecuteParams, +} from '../types' + +describe('CryptoBox Types', () => { + describe('TOKEN_DURATION_MS', () => { + it('should have correct milliseconds for 5min', () => { + expect(TOKEN_DURATION_MS['5min']).toBe(5 * 60 * 1000) + }) + + it('should have correct milliseconds for 30min', () => { + expect(TOKEN_DURATION_MS['30min']).toBe(30 * 60 * 1000) + }) + + it('should have correct milliseconds for 2hour', () => { + expect(TOKEN_DURATION_MS['2hour']).toBe(2 * 60 * 60 * 1000) + }) + + it('should have correct milliseconds for 1day', () => { + expect(TOKEN_DURATION_MS['1day']).toBe(24 * 60 * 60 * 1000) + }) + }) + + describe('CryptoBoxErrorCodes', () => { + it('should have TOKEN_NOT_FOUND code', () => { + expect(CryptoBoxErrorCodes.TOKEN_NOT_FOUND).toBe(4100) + }) + + it('should have MINIAPP_MISMATCH code', () => { + expect(CryptoBoxErrorCodes.MINIAPP_MISMATCH).toBe(4101) + }) + + it('should have TOKEN_EXPIRED code', () => { + expect(CryptoBoxErrorCodes.TOKEN_EXPIRED).toBe(4102) + }) + + it('should have ACTION_NOT_PERMITTED code', () => { + expect(CryptoBoxErrorCodes.ACTION_NOT_PERMITTED).toBe(4103) + }) + + it('should have USER_REJECTED code', () => { + expect(CryptoBoxErrorCodes.USER_REJECTED).toBe(4001) + }) + + it('should have ADDRESS_MISMATCH code', () => { + expect(CryptoBoxErrorCodes.ADDRESS_MISMATCH).toBe(4105) + }) + }) + + describe('Type guards', () => { + it('should accept valid CryptoAction values', () => { + const actions: CryptoAction[] = ['asymmetricEncrypt', 'sign'] + expect(actions).toHaveLength(2) + }) + + it('should accept valid TokenDuration values', () => { + const durations: TokenDuration[] = ['5min', '30min', '2hour', '1day'] + expect(durations).toHaveLength(4) + }) + + it('should create valid CryptoToken', () => { + const token: CryptoToken = { + tokenId: 'test-uuid', + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['asymmetricEncrypt'], + expiresAt: Date.now() + 300000, + createdAt: Date.now(), + } + expect(token.tokenId).toBe('test-uuid') + expect(token.actions).toContain('asymmetricEncrypt') + }) + + it('should create valid StoredToken with encryptedPayload', () => { + const storedToken: StoredToken = { + tokenId: 'test-uuid', + miniappId: 'app.test.dweb', + walletId: 'wallet-1', + address: 'bfm1test', + actions: ['sign'], + expiresAt: Date.now() + 300000, + createdAt: Date.now(), + encryptedPayload: 'encrypted-json-data', + } + expect(storedToken.encryptedPayload).toBe('encrypted-json-data') + }) + + it('should create valid RequestCryptoTokenParams', () => { + const params: RequestCryptoTokenParams = { + actions: ['asymmetricEncrypt', 'sign'], + duration: '30min', + address: 'bfm1test', + } + expect(params.duration).toBe('30min') + }) + + it('should create valid CryptoExecuteParams for asymmetricEncrypt', () => { + const params: CryptoExecuteParams = { + tokenId: 'test-uuid', + sessionSecret: 'secret-123', + action: 'asymmetricEncrypt', + params: { + data: 'hello', + recipientPublicKey: '0xabc123', + }, + } + expect(params.action).toBe('asymmetricEncrypt') + }) + + it('should create valid CryptoExecuteParams for sign', () => { + const params: CryptoExecuteParams = { + tokenId: 'test-uuid', + sessionSecret: 'secret-123', + action: 'sign', + params: { + data: 'message to sign', + }, + } + expect(params.action).toBe('sign') + }) + + it('should create valid CryptoExecuteParams with address for validation', () => { + const params: CryptoExecuteParams = { + tokenId: 'test-uuid', + sessionSecret: 'secret-123', + action: 'sign', + params: { + data: 'message to sign', + }, + address: 'bfm1test', // 用于安全验证 + } + expect(params.address).toBe('bfm1test') + }) + }) +}) diff --git a/src/services/crypto-box/executor.ts b/src/services/crypto-box/executor.ts new file mode 100644 index 000000000..950a6532a --- /dev/null +++ b/src/services/crypto-box/executor.ts @@ -0,0 +1,231 @@ +/** + * Crypto 黑盒操作执行器 + * + * 在验证 Token 后执行实际的加密操作: + * - asymmetricEncrypt: 使用 Ed25519 进行非对称加密 + * - sign: 使用 Ed25519 进行签名 + * + * 安全设计: + * - 操作在内部完成,私钥不暴露 + * - patternKey 从加密 Payload 中获取 + * - 所有验证数据从加密 Payload 获取(不信任明文副本) + */ + +import { tokenStore } from './token-store' +import type { + CryptoExecuteParams, + CryptoExecuteResponse, + AsymmetricEncryptParams, + SignParams, +} from './types' + +// 导入加密工具 +import { + signMessage, + bytesToHex, + hexToBytes, + createBioforestKeypair, + publicKeyToBioforestAddress, +} from '@/lib/crypto' + +// 导入钱包存储服务 +import { walletStorageService } from '@/services/wallet-storage' + +/** + * 加密操作执行器 + */ +class CryptoExecutor { + /** + * 执行加密操作 + * + * @param params 执行参数(含 sessionSecret) + * @param callerMiniappId 调用者 miniapp ID + */ + async execute( + params: CryptoExecuteParams, + callerMiniappId: string + ): Promise { + // 1. 验证 Token 并解密 Payload + const validation = await tokenStore.validateToken( + params.tokenId, + params.sessionSecret, + callerMiniappId, + params.action + ) + + if (!validation.valid) { + const { CryptoBoxErrorCodes } = await import('./types') + const errorCodeMap: Record = { + 'TOKEN_NOT_FOUND': CryptoBoxErrorCodes.TOKEN_NOT_FOUND, + 'MINIAPP_MISMATCH': CryptoBoxErrorCodes.MINIAPP_MISMATCH, + 'TOKEN_EXPIRED': CryptoBoxErrorCodes.TOKEN_EXPIRED, + 'ACTION_NOT_PERMITTED': CryptoBoxErrorCodes.ACTION_NOT_PERMITTED, + 'INVALID_SESSION_SECRET': CryptoBoxErrorCodes.INVALID_SESSION_SECRET, + 'ADDRESS_MISMATCH': CryptoBoxErrorCodes.ADDRESS_MISMATCH, + } + throw Object.assign( + new Error(`Token validation failed: ${validation.error}`), + { code: errorCodeMap[validation.error] } + ) + } + + const { payload } = validation + + // 2. 安全验证:如果调用方提供了地址,必须与 Token 中的地址匹配 + // 这可以防止 miniapp 使用旧 Token 操作新地址 + if (params.address && params.address !== payload.address) { + const { CryptoBoxErrorCodes } = await import('./types') + throw Object.assign( + new Error(`Address mismatch: requested ${params.address} but token is for ${payload.address}`), + { code: CryptoBoxErrorCodes.ADDRESS_MISMATCH } + ) + } + + // 3. 从 Payload 获取 patternKey 并解密钱包私钥 + // 注意:必须使用 walletId 限制查找范围,防止跨钱包使用 Token + const keypair = await this.getKeypairForWallet(payload.walletId, payload.address, payload.patternKey) + + // 4. 执行操作 + let result: { result: string; publicKey: string } + + switch (params.action) { + case 'asymmetricEncrypt': + result = await this.executeAsymmetricEncrypt( + params.params as AsymmetricEncryptParams, + keypair + ) + break + case 'sign': + result = await this.executeSign( + params.params as SignParams, + keypair + ) + break + default: + throw new Error(`Unknown action: ${params.action}`) + } + + // 5. 返回结果,附带 Token 绑定的地址(让 miniapp 知道这个操作使用的是哪个地址) + return { + ...result, + address: payload.address, + } + } + + /** + * 获取指定钱包和地址对应的密钥对 + * + * 安全:必须同时验证 walletId 和 address,防止 Token 被用于其他钱包 + */ + private async getKeypairForWallet( + walletId: string, + address: string, + patternKey: string + ): Promise<{ secretKey: Uint8Array; publicKey: Uint8Array }> { + // 只从指定的钱包获取 mnemonic + try { + const mnemonic = await walletStorageService.getMnemonic(walletId, patternKey) + const keypair = createBioforestKeypair(mnemonic) + + // 地址前缀(b 或 c) + const prefix = address.charAt(0) + + // 验证派生的地址是否与 Token 中的目标地址匹配 + const derivedAddress = publicKeyToBioforestAddress(keypair.publicKey, prefix) + if (derivedAddress !== address) { + throw new Error(`Address mismatch: token address ${address} does not match wallet`) + } + + return keypair + } catch (err) { + if (err instanceof Error && err.message.includes('Address mismatch')) { + throw err + } + throw new Error(`Failed to get keypair for wallet ${walletId}: invalid patternKey or wallet not found`) + } + } + + /** + * 将各种格式的公钥转换为 hex 字符串 + */ + private normalizePublicKey(publicKey: unknown): string { + if (typeof publicKey === 'string') { + return publicKey + } + if (publicKey && typeof publicKey === 'object' && 'type' in publicKey && 'data' in publicKey) { + const bufferLike = publicKey as { type: string; data: number[] } + if (bufferLike.type === 'Buffer' && Array.isArray(bufferLike.data)) { + return bufferLike.data.map(b => b.toString(16).padStart(2, '0')).join('') + } + } + if (Array.isArray(publicKey) || publicKey instanceof Uint8Array) { + return Array.from(publicKey as ArrayLike) + .map(b => b.toString(16).padStart(2, '0')) + .join('') + } + throw new Error(`Invalid publicKey format: ${typeof publicKey}`) + } + + /** + * 执行非对称加密 + * + * 使用 X25519 ECDH + 加密,与 BFMetaSignUtil.asymmetricEncrypt 兼容 + */ + private async executeAsymmetricEncrypt( + params: AsymmetricEncryptParams, + keypair: { secretKey: Uint8Array; publicKey: Uint8Array } + ): Promise> { + const recipientPubKey = hexToBytes(this.normalizePublicKey(params.recipientPublicKey)) + const messageBytes = new TextEncoder().encode(params.data) + + // 导入 tweetnacl 和 ed2curve(用于 Ed25519 -> X25519 转换) + const nacl = await import('tweetnacl') + const ed2curve = await import('ed2curve') + + // 将 Ed25519 公钥/私钥转换为 X25519(Curve25519) + const curveRecipientPK = ed2curve.convertPublicKey(recipientPubKey) + const curveSecretKey = ed2curve.convertSecretKey(keypair.secretKey) + + if (!curveRecipientPK || !curveSecretKey) { + throw new Error('Failed to convert Ed25519 keys to X25519') + } + + // 使用固定的全 0 nonce(与 BFMetaSignUtil 兼容) + const nonce = new Uint8Array(24) + + // 加密 + const encrypted = nacl.box( + messageBytes, + nonce, + curveRecipientPK, + curveSecretKey + ) + + // 返回 encryptedMessage (不含 nonce),与 BFMetaSignUtil.asymmetricEncrypt 一致 + return { + result: bytesToHex(encrypted), + publicKey: bytesToHex(keypair.publicKey), + } + } + + /** + * 执行 ECDSA 签名 + */ + private async executeSign( + params: SignParams, + keypair: { secretKey: Uint8Array; publicKey: Uint8Array } + ): Promise> { + const messageBytes = new TextEncoder().encode(params.data) + + // 使用 Ed25519 签名 + const signature = signMessage(messageBytes, keypair.secretKey) + + return { + result: bytesToHex(signature), + publicKey: bytesToHex(keypair.publicKey), + } + } +} + +// 单例导出 +export const cryptoExecutor = new CryptoExecutor() diff --git a/src/services/crypto-box/index.ts b/src/services/crypto-box/index.ts new file mode 100644 index 000000000..b519d0ce9 --- /dev/null +++ b/src/services/crypto-box/index.ts @@ -0,0 +1,18 @@ +/** + * Crypto 黑盒服务 - 主入口 + * + * 提供 Token 授权系统的对外接口 + */ + +export { tokenStore } from './token-store' +export { cryptoExecutor } from './executor' +export * from './types' + +import { tokenStore } from './token-store' + +/** + * 初始化 Crypto 黑盒服务 + */ +export async function initializeCryptoBox(): Promise { + await tokenStore.initialize() +} diff --git a/src/services/crypto-box/token-store.ts b/src/services/crypto-box/token-store.ts new file mode 100644 index 000000000..46cc74c07 --- /dev/null +++ b/src/services/crypto-box/token-store.ts @@ -0,0 +1,371 @@ +/** + * Crypto 黑盒 Token 存储服务 + * + * 使用 IndexedDB 持久化 Token,支持: + * - Token 创建/读取/删除 + * - 过期清理 + * - 加密 Payload 存储与解密 + * + * 安全设计: + * - patternKey 加密存储在 encryptedPayload 中 + * - sessionSecret 派生自 walletId + patternKey + miniappId + tokenId + * - 执行时从加密 Payload 获取真实数据 + */ + +import { openDB, type DBSchema, type IDBPDatabase } from 'idb' +import { encrypt, decrypt } from '@/lib/crypto' +import type { + CryptoToken, + StoredToken, + CryptoAction, + TokenDuration, + TokenValidationResult, + TokenPayload, +} from './types' + +const DB_NAME = 'crypto-box-db' +const DB_VERSION = 2 // 升级版本以支持新的 schema +const STORE_NAME = 'tokens' + +interface CryptoBoxDBSchema extends DBSchema { + tokens: { + key: string + value: StoredToken + indexes: { + 'by-miniapp': string + 'by-wallet': string + 'by-expires': number + } + } +} + +/** + * Token 存储服务 + */ +class TokenStore { + private db: IDBPDatabase | null = null + private initialized = false + + /** + * 初始化存储 + */ + async initialize(): Promise { + if (this.initialized) return + + this.db = await openDB(DB_NAME, DB_VERSION, { + upgrade(db, _oldVersion) { + // 删除旧版本的 store + if (db.objectStoreNames.contains(STORE_NAME)) { + db.deleteObjectStore(STORE_NAME) + } + // 创建新的 store + const store = db.createObjectStore(STORE_NAME, { keyPath: 'tokenId' }) + store.createIndex('by-miniapp', 'miniappId') + store.createIndex('by-wallet', 'walletId') + store.createIndex('by-expires', 'expiresAt') + }, + }) + + this.initialized = true + + // 启动时清理过期 Token + await this.cleanupExpired() + } + + private ensureInitialized(): void { + if (!this.initialized || !this.db) { + throw new Error('TokenStore not initialized') + } + } + + /** + * 生成 Token ID + */ + generateTokenId(): string { + return crypto.randomUUID() + } + + /** + * 计算 SHA-256 哈希 + */ + private async sha256(data: string): Promise { + const encoder = new TextEncoder() + const dataBuffer = encoder.encode(data) + const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + } + + /** + * 派生 sessionSecret + * sessionSecret = SHA256(walletId + patternKey + miniappId + tokenId) + */ + async deriveSessionSecret( + walletId: string, + patternKey: string, + miniappId: string, + tokenId: string + ): Promise { + return this.sha256(`${walletId}:${patternKey}:${miniappId}:${tokenId}`) + } + + /** + * 创建并存储 Token + * + * @returns Token 信息和 sessionSecret + */ + async createToken(params: { + miniappId: string + walletId: string + address: string + actions: CryptoAction[] + duration: TokenDuration + patternKey: string + }): Promise<{ token: CryptoToken; sessionSecret: string }> { + this.ensureInitialized() + + const { TOKEN_DURATION_MS: durationMs } = await import('./types') + const now = Date.now() + const tokenId = this.generateTokenId() + const expiresAt = now + durationMs[params.duration] + + // 派生 sessionSecret + const sessionSecret = await this.deriveSessionSecret( + params.walletId, + params.patternKey, + params.miniappId, + tokenId + ) + + // 构建 Payload 并加密 + const payload: TokenPayload = { + patternKey: params.patternKey, + miniappId: params.miniappId, + walletId: params.walletId, + address: params.address, + actions: params.actions, + expiresAt, + } + const encryptedPayload = JSON.stringify(await encrypt(JSON.stringify(payload), sessionSecret)) + + // 存储 Token(明文副本 + 加密 Payload) + const storedToken: StoredToken = { + tokenId, + miniappId: params.miniappId, + walletId: params.walletId, + address: params.address, + actions: params.actions, + expiresAt, + createdAt: now, + encryptedPayload, + } + + await this.db!.put(STORE_NAME, storedToken) + + // 返回给调用者 + const { encryptedPayload: _, ...token } = storedToken + return { token, sessionSecret } + } + + /** + * 解密 Token Payload + */ + async decryptPayload(token: StoredToken, sessionSecret: string): Promise { + try { + const encryptedData = JSON.parse(token.encryptedPayload) + const decrypted = await decrypt(encryptedData, sessionSecret) + return JSON.parse(decrypted) as TokenPayload + } catch { + return null + } + } + + /** + * 获取 Token + */ + async getToken(tokenId: string): Promise { + this.ensureInitialized() + const token = await this.db!.get(STORE_NAME, tokenId) + return token ?? null + } + + /** + * 删除 Token + */ + async deleteToken(tokenId: string): Promise { + this.ensureInitialized() + await this.db!.delete(STORE_NAME, tokenId) + } + + /** + * 验证 Token 并解密 Payload + * + * 从加密 Payload 获取真实数据进行验证(不信任明文副本) + */ + async validateToken( + tokenId: string, + sessionSecret: string, + callerMiniappId: string, + requestedAction: CryptoAction + ): Promise { + this.ensureInitialized() + + const token = await this.getToken(tokenId) + + // 1. Token 不存在 + if (!token) { + return { valid: false, error: 'TOKEN_NOT_FOUND' } + } + + // 2. 解密 Payload + const payload = await this.decryptPayload(token, sessionSecret) + if (!payload) { + return { valid: false, error: 'INVALID_SESSION_SECRET' } + } + + // 3. 从 Payload 验证 miniappId(真实数据) + if (payload.miniappId !== callerMiniappId) { + return { valid: false, error: 'MINIAPP_MISMATCH' } + } + + // 4. 从 Payload 验证过期时间(真实数据) + if (Date.now() > payload.expiresAt) { + await this.deleteToken(tokenId) + return { valid: false, error: 'TOKEN_EXPIRED' } + } + + // 5. 从 Payload 验证操作权限(真实数据) + if (!payload.actions.includes(requestedAction)) { + return { valid: false, error: 'ACTION_NOT_PERMITTED' } + } + + return { valid: true, token, payload } + } + + /** + * 获取 Token 信息(用于 bio_getCryptoTokenInfo) + * + * 验证 Token 并返回绑定的地址等信息,不检查 action + */ + async getTokenInfo( + tokenId: string, + sessionSecret: string, + callerMiniappId: string + ): Promise<{ + valid: boolean + address: string + expiresAt: number + actions: CryptoAction[] + invalidReason?: 'TOKEN_NOT_FOUND' | 'TOKEN_EXPIRED' | 'INVALID_SESSION_SECRET' | 'MINIAPP_MISMATCH' + }> { + this.ensureInitialized() + + const token = await this.getToken(tokenId) + + // 1. Token 不存在 + if (!token) { + return { + valid: false, + address: '', + expiresAt: 0, + actions: [], + invalidReason: 'TOKEN_NOT_FOUND', + } + } + + // 2. 解密 Payload + const payload = await this.decryptPayload(token, sessionSecret) + if (!payload) { + return { + valid: false, + address: '', + expiresAt: 0, + actions: [], + invalidReason: 'INVALID_SESSION_SECRET', + } + } + + // 3. 验证 miniappId + if (payload.miniappId !== callerMiniappId) { + return { + valid: false, + address: '', + expiresAt: 0, + actions: [], + invalidReason: 'MINIAPP_MISMATCH', + } + } + + // 4. 检查过期 + if (Date.now() > payload.expiresAt) { + await this.deleteToken(tokenId) + return { + valid: false, + address: payload.address, + expiresAt: payload.expiresAt, + actions: payload.actions, + invalidReason: 'TOKEN_EXPIRED', + } + } + + // Token 有效 + return { + valid: true, + address: payload.address, + expiresAt: payload.expiresAt, + actions: payload.actions, + } + } + + /** + * 清理过期 Token + */ + async cleanupExpired(): Promise { + this.ensureInitialized() + + const now = Date.now() + const tx = this.db!.transaction(STORE_NAME, 'readwrite') + const index = tx.store.index('by-expires') + + let deletedCount = 0 + let cursor = await index.openCursor(IDBKeyRange.upperBound(now)) + + while (cursor) { + await cursor.delete() + deletedCount++ + cursor = await cursor.continue() + } + + await tx.done + return deletedCount + } + + /** + * 获取 miniapp 的所有有效 Token(明文副本) + */ + async getTokensByMiniapp(miniappId: string): Promise { + this.ensureInitialized() + + const now = Date.now() + const tokens = await this.db!.getAllFromIndex(STORE_NAME, 'by-miniapp', miniappId) + + // 过滤未过期的 Token 并移除 encryptedPayload + return tokens + .filter(t => t.expiresAt > now) + .map(({ encryptedPayload: _, ...token }) => token) + } + + /** + * 关闭数据库连接 + */ + close(): void { + if (this.db) { + this.db.close() + this.db = null + this.initialized = false + } + } +} + +// 单例导出 +export const tokenStore = new TokenStore() diff --git a/src/services/crypto-box/types.ts b/src/services/crypto-box/types.ts new file mode 100644 index 000000000..fcc38cad1 --- /dev/null +++ b/src/services/crypto-box/types.ts @@ -0,0 +1,251 @@ +/** + * Crypto 黑盒 Token 授权系统 - 类型定义 + */ + +// ==================== Token 相关类型 ==================== + +/** + * 授权的加密操作类型 + * - asymmetricEncrypt: 非对称加密(如 RWA 登录) + * - sign: ECDSA 签名 + * + * 注意:BioChain 交易签名不在此列,必须使用专门的 bio_signTransaction + */ +export type CryptoAction = 'asymmetricEncrypt' | 'sign' + +/** + * Token 授权时长 + */ +export type TokenDuration = '5min' | '30min' | '2hour' | '1day' + +/** + * Token 时长映射(毫秒) + */ +export const TOKEN_DURATION_MS: Record = { + '5min': 5 * 60 * 1000, + '30min': 30 * 60 * 1000, + '2hour': 2 * 60 * 60 * 1000, + '1day': 24 * 60 * 60 * 1000, +} + +/** + * Token 时长选项列表(用于 UI Select) + */ +export const TOKEN_DURATION_OPTIONS: TokenDuration[] = ['5min', '30min', '2hour', '1day'] + +/** + * Crypto Token 结构(明文副本,用于查询展示) + */ +export interface CryptoToken { + /** Token 唯一标识 */ + tokenId: string + /** 授权的 miniapp ID */ + miniappId: string + /** 钱包 ID */ + walletId: string + /** 使用的钱包地址 */ + address: string + /** 授权的操作列表 */ + actions: CryptoAction[] + /** 过期时间戳 */ + expiresAt: number + /** 创建时间 */ + createdAt: number +} + +/** + * 加密 Payload 内容(真实数据源) + */ +export interface TokenPayload { + /** 手势密码 */ + patternKey: string + /** 授权的 miniapp ID */ + miniappId: string + /** 钱包 ID */ + walletId: string + /** 使用的钱包地址 */ + address: string + /** 授权的操作列表 */ + actions: CryptoAction[] + /** 过期时间戳 */ + expiresAt: number +} + +/** + * 存储在 IndexedDB 中的 Token 记录 + * 明文副本 + 加密 Payload + */ +export interface StoredToken extends CryptoToken { + /** 加密的 Payload(真实数据源) */ + encryptedPayload: string +} + +// ==================== API 请求/响应类型 ==================== + +/** + * bio_requestCryptoToken 请求参数 + */ +export interface RequestCryptoTokenParams { + /** 需要的操作权限 */ + actions: CryptoAction[] + /** 授权时长 */ + duration: TokenDuration + /** 使用的地址 */ + address: string + /** 链 ID(可选,用于 UI 显示) */ + chainId?: string +} + +/** + * bio_requestCryptoToken 响应 + */ +export interface RequestCryptoTokenResponse { + /** Token ID(返回给 miniapp) */ + tokenId: string + /** Session Secret(用于后续执行的解密密钥) */ + sessionSecret: string + /** 过期时间戳 */ + expiresAt: number + /** 授权的操作 */ + grantedActions: CryptoAction[] + /** Token 绑定的地址(miniapp 应缓存此地址,地址变化时需重新请求 Token) */ + address: string +} + +/** + * 非对称加密参数 + */ +export interface AsymmetricEncryptParams { + /** 要加密的数据 */ + data: string + /** 接收方公钥(hex) */ + recipientPublicKey: string +} + +/** + * 签名参数 + */ +export interface SignParams { + /** 要签名的数据 */ + data: string +} + +/** + * bio_cryptoExecute 请求参数 + */ +export interface CryptoExecuteParams { + /** Token ID */ + tokenId: string + /** Session Secret(解密 Payload 的密钥) */ + sessionSecret: string + /** 要执行的操作 */ + action: CryptoAction + /** 操作参数 */ + params: AsymmetricEncryptParams | SignParams + /** 期望使用的地址(安全验证:必须与 Token 中的地址匹配) */ + address?: string +} + +/** + * bio_cryptoExecute 响应 + */ +export interface CryptoExecuteResponse { + /** 操作结果(hex 编码)*/ + result: string + /** 执行时使用的公钥(hex 编码)*/ + publicKey: string + /** Token 绑定的地址(miniapp 应使用此地址,而非自己缓存的地址) */ + address: string +} + +/** + * bio_getCryptoTokenInfo 请求参数 + */ +export interface GetCryptoTokenInfoParams { + /** Token ID */ + tokenId: string + /** Session Secret(用于解密和验证) */ + sessionSecret: string +} + +/** + * bio_getCryptoTokenInfo 响应 + */ +export interface GetCryptoTokenInfoResponse { + /** Token 是否有效 */ + valid: boolean + /** Token 绑定的地址 */ + address: string + /** 过期时间戳 */ + expiresAt: number + /** 授权的操作列表 */ + actions: CryptoAction[] + /** 无效原因(仅当 valid=false 时) */ + invalidReason?: 'TOKEN_NOT_FOUND' | 'TOKEN_EXPIRED' | 'INVALID_SESSION_SECRET' | 'MINIAPP_MISMATCH' +} + +// ==================== 验证相关类型 ==================== + +/** + * Token 验证结果 + */ +export type TokenValidationResult = + | { valid: true; token: StoredToken; payload: TokenPayload } + | { valid: false; error: TokenValidationError } + +/** + * Token 验证错误类型 + */ +export type TokenValidationError = + | 'TOKEN_NOT_FOUND' + | 'MINIAPP_MISMATCH' + | 'TOKEN_EXPIRED' + | 'ACTION_NOT_PERMITTED' + | 'INVALID_SESSION_SECRET' + +// ==================== 错误代码 ==================== + +export const CryptoBoxErrorCodes = { + /** Token 未找到 */ + TOKEN_NOT_FOUND: 4100, + /** miniapp ID 不匹配 */ + MINIAPP_MISMATCH: 4101, + /** Token 已过期 */ + TOKEN_EXPIRED: 4102, + /** 操作未授权 */ + ACTION_NOT_PERMITTED: 4103, + /** Session Secret 无效 */ + INVALID_SESSION_SECRET: 4104, + /** 地址不匹配(请求地址与 Token 绑定地址不一致) */ + ADDRESS_MISMATCH: 4105, + /** 用户拒绝授权 */ + USER_REJECTED: 4001, + /** 内部错误 */ + INTERNAL_ERROR: -32603, +} as const + +// ==================== UI 显示标签 ==================== + +/** + * 操作描述映射(用于 UI 显示) + */ +export const CRYPTO_ACTION_LABELS: Record = { + asymmetricEncrypt: { + name: '非对称加密', + description: '使用您的私钥进行非对称加密', + }, + sign: { + name: '数据签名', + description: '使用您的私钥进行签名', + }, +} + +/** + * 时长描述映射(用于 UI 显示) + */ +export const TOKEN_DURATION_LABELS: Record = { + '5min': '5 分钟', + '30min': '30 分钟', + '2hour': '2 小时', + '1day': '1 天', +} diff --git a/src/services/ecosystem/__tests__/bridge.test.ts b/src/services/ecosystem/__tests__/bridge.test.ts index 90bd43539..392463789 100644 --- a/src/services/ecosystem/__tests__/bridge.test.ts +++ b/src/services/ecosystem/__tests__/bridge.test.ts @@ -11,6 +11,20 @@ vi.mock('../../miniapp-runtime', () => ({ }, })) +// Mock permissions module - default to allowing all permissions +const mockHasPermission = vi.fn().mockReturnValue(true) +const mockIsSensitiveMethod = vi.fn().mockImplementation((method: string) => + ['bio_requestAccounts', 'bio_signMessage', 'bio_signTypedData', 'bio_signTransaction', 'bio_sendTransaction'].includes(method) +) +const mockGrantPermissions = vi.fn() + +vi.mock('../permissions', () => ({ + hasPermission: (...args: unknown[]) => mockHasPermission(...args), + isSensitiveMethod: (...args: unknown[]) => mockIsSensitiveMethod(...args), + grantPermissions: (...args: unknown[]) => mockGrantPermissions(...args), +})) + + describe('PostMessageBridge', () => { let bridge: PostMessageBridge @@ -77,7 +91,7 @@ describe('PostMessageBridge', () => { it('posts message to iframe contentWindow', () => { const iframe = document.createElement('iframe') const mockPostMessage = vi.fn() - + // Mock contentWindow Object.defineProperty(iframe, 'contentWindow', { value: { postMessage: mockPostMessage }, @@ -105,13 +119,13 @@ describe('PostMessageBridge', () => { it('stores manifest permissions when attaching', () => { const iframe = document.createElement('iframe') - + // Attach with specific permissions bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts', 'bio_signMessage']) - + // Detach and re-attach with different permissions bridge.attach(iframe, 'test-app-2', 'Test App 2', ['bio_createTransaction']) - + // The bridge should have updated its internal permissions // We can't directly test private fields, but we verify attach doesn't throw expect(true).toBe(true) @@ -119,7 +133,7 @@ describe('PostMessageBridge', () => { it('attaches with empty permissions array', () => { const iframe = document.createElement('iframe') - + // Should not throw with empty permissions expect(() => { bridge.attach(iframe, 'test-app', 'Test App', []) @@ -128,7 +142,7 @@ describe('PostMessageBridge', () => { it('attaches without permissions parameter (defaults to empty)', () => { const iframe = document.createElement('iframe') - + // Should not throw without permissions expect(() => { bridge.attach(iframe, 'test-app', 'Test App') @@ -138,13 +152,13 @@ describe('PostMessageBridge', () => { describe('permission rules documentation', () => { // These are documentation tests that verify our understanding of the permission system - + it('should reject undeclared bio methods (integration behavior)', () => { // When a miniapp calls bio_signMessage but manifest only declares: // ["bio_requestAccounts", "bio_selectAccount"] // The bridge should return: // { error: { code: 4100, message: "Permission not declared in manifest: bio_signMessage" } } - + // This is verified by E2E tests and the forge miniapp fix in PR #202 expect(BioErrorCodes.UNAUTHORIZED).toBe(4100) }) @@ -154,7 +168,7 @@ describe('PostMessageBridge', () => { // - bio_accounts -> bio_requestAccounts // - bio_selectAccount -> bio_requestAccounts // - bio_pickWallet -> bio_requestAccounts - + // So declaring bio_requestAccounts allows all these methods const accountRelatedMethods = ['bio_accounts', 'bio_selectAccount', 'bio_pickWallet'] expect(accountRelatedMethods).toContain('bio_selectAccount') @@ -166,4 +180,225 @@ describe('PostMessageBridge', () => { expect(skipMethods).toContain('bio_connect') }) }) + + describe('message handling', () => { + /** + * Helper to create a mock MessageEvent + */ + function createMockMessageEvent( + data: unknown, + origin: string, + source: Window | null + ): MessageEvent { + return new MessageEvent('message', { + data, + origin, + source, + }) + } + + /** + * Helper to create an iframe with mocked contentWindow + */ + function createMockIframe(src: string): HTMLIFrameElement { + const iframe = document.createElement('iframe') + iframe.src = src + + const mockContentWindow = { + postMessage: vi.fn(), + } + + Object.defineProperty(iframe, 'contentWindow', { + value: mockContentWindow, + writable: true, + configurable: true, + }) + + return iframe + } + + it('ignores non-bio message types', () => { + const iframe = createMockIframe('http://localhost:19200') + const handler = vi.fn() + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + // Dispatch a non-bio message + const event = createMockMessageEvent( + { type: 'some_other_message', data: 'test' }, + 'http://localhost:19200', + iframe.contentWindow + ) + + window.dispatchEvent(event) + + expect(handler).not.toHaveBeenCalled() + }) + + it('ignores messages without proper structure', () => { + const iframe = createMockIframe('http://localhost:19200') + const handler = vi.fn() + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + // Dispatch invalid messages + window.dispatchEvent(createMockMessageEvent(null, 'http://localhost:19200', iframe.contentWindow)) + window.dispatchEvent(createMockMessageEvent('string', 'http://localhost:19200', iframe.contentWindow)) + window.dispatchEvent(createMockMessageEvent({ noType: true }, 'http://localhost:19200', iframe.contentWindow)) + + expect(handler).not.toHaveBeenCalled() + }) + + it('processes bio_request from matching origin', async () => { + const iframe = createMockIframe('http://localhost:19200') + const handler = vi.fn().mockResolvedValue(['0x123']) + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + const event = createMockMessageEvent( + { + type: 'bio_request', + id: 'test-id-1', + method: 'bio_requestAccounts', + params: [], + }, + 'http://localhost:19200', + iframe.contentWindow + ) + + window.dispatchEvent(event) + + // Wait for async processing + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(handler).toHaveBeenCalled() + }) + + it('allows localhost mixed content in development (HTTP iframe in HTTPS host)', async () => { + const iframe = createMockIframe('http://localhost:19200') + const handler = vi.fn().mockResolvedValue(['0x123']) + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + // Simulate HTTP->HTTPS scenario where browser reports parent origin + const event = createMockMessageEvent( + { + type: 'bio_request', + id: 'test-id-2', + method: 'bio_requestAccounts', + params: [], + }, + 'https://localhost:5173', // Different protocol, same hostname + iframe.contentWindow + ) + + window.dispatchEvent(event) + + await new Promise(resolve => setTimeout(resolve, 10)) + + // Should still process because both are localhost + expect(handler).toHaveBeenCalled() + }) + + it('rejects messages from non-localhost mismatched origin', async () => { + const iframe = createMockIframe('https://app.example.com') + const handler = vi.fn().mockResolvedValue(['0x123']) + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + const event = createMockMessageEvent( + { + type: 'bio_request', + id: 'test-id-3', + method: 'bio_requestAccounts', + params: [], + }, + 'https://malicious.com', // Different origin + iframe.contentWindow + ) + + window.dispatchEvent(event) + + await new Promise(resolve => setTimeout(resolve, 10)) + + expect(handler).not.toHaveBeenCalled() + }) + + it('triggers permission request callback for sensitive methods', async () => { + // Set hasPermission to return false to trigger the permission request flow + mockHasPermission.mockReturnValue(false) + + const iframe = createMockIframe('http://localhost:19200') + const handler = vi.fn().mockResolvedValue(['0x123']) + const permissionCallback = vi.fn().mockResolvedValue(true) + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.setPermissionRequestCallback(permissionCallback) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + const event = createMockMessageEvent( + { + type: 'bio_request', + id: 'test-id-4', + method: 'bio_requestAccounts', + params: [], + }, + 'http://localhost:19200', + iframe.contentWindow + ) + + window.dispatchEvent(event) + + await new Promise(resolve => setTimeout(resolve, 50)) + + expect(permissionCallback).toHaveBeenCalledWith( + 'test-app', + 'Test App', + ['bio_requestAccounts'] + ) + + // Reset the mock to default for other tests + mockHasPermission.mockReturnValue(true) + }) + + it('rejects when permission callback returns false', async () => { + // Set hasPermission to return false to trigger the permission request flow + mockHasPermission.mockReturnValue(false) + + const iframe = createMockIframe('http://localhost:19200') + const handler = vi.fn().mockResolvedValue(['0x123']) + const permissionCallback = vi.fn().mockResolvedValue(false) + + bridge.registerHandler('bio_requestAccounts', handler) + bridge.setPermissionRequestCallback(permissionCallback) + bridge.attach(iframe, 'test-app', 'Test App', ['bio_requestAccounts']) + + const event = createMockMessageEvent( + { + type: 'bio_request', + id: 'test-id-5', + method: 'bio_requestAccounts', + params: [], + }, + 'http://localhost:19200', + iframe.contentWindow + ) + + window.dispatchEvent(event) + + await new Promise(resolve => setTimeout(resolve, 50)) + + // Handler should NOT be called because permission was denied + expect(handler).not.toHaveBeenCalled() + + // Reset the mock to default for other tests + mockHasPermission.mockReturnValue(true) + }) + }) }) + diff --git a/src/services/ecosystem/__tests__/wallet-handlers.test.ts b/src/services/ecosystem/__tests__/wallet-handlers.test.ts index baadafe5c..e0d69ba00 100644 --- a/src/services/ecosystem/__tests__/wallet-handlers.test.ts +++ b/src/services/ecosystem/__tests__/wallet-handlers.test.ts @@ -1,24 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { handleGetBalance } from '../handlers/wallet' import { BioErrorCodes } from '../types' -// Mock dependencies -vi.mock('@/services/chain-config', () => ({ - chainConfigService: { - getBiowalletApi: vi.fn(), - }, -})) - -vi.mock('@/services/bioforest-sdk', () => ({ - getAccountBalance: vi.fn(), -})) - -import { chainConfigService } from '@/services/chain-config' -import { getAccountBalance } from '@/services/bioforest-sdk' - -const mockGetBiowalletApi = vi.mocked(chainConfigService.getBiowalletApi) -const mockGetAccountBalance = vi.mocked(getAccountBalance) - describe('handleGetBalance', () => { const mockContext = { appId: 'test-app', @@ -27,12 +9,34 @@ describe('handleGetBalance', () => { permissions: ['bio_getBalance'], } - beforeEach(() => { - vi.clearAllMocks() + let handleGetBalance: typeof import('../handlers/wallet').handleGetBalance + let mockFetch: ReturnType + let mockGetChainProvider: ReturnType + + beforeEach(async () => { + // Reset modules for clean state + vi.resetModules() + + // Create fresh mocks + mockFetch = vi.fn() + mockGetChainProvider = vi.fn(() => ({ + nativeBalance: { + fetch: mockFetch, + }, + })) + + // Mock chain-adapter/providers (the actual import path used by handler) + vi.doMock('@/services/chain-adapter/providers', () => ({ + getChainProvider: mockGetChainProvider, + })) + + // Now import the handler module + const walletModule = await import('../handlers/wallet') + handleGetBalance = walletModule.handleGetBalance }) afterEach(() => { - vi.resetAllMocks() + vi.doUnmock('@/services/chain-adapter/providers') }) describe('parameter validation', () => { @@ -65,46 +69,40 @@ describe('handleGetBalance', () => { }) }) - describe('chain without biowallet API', () => { - it('returns "0" when chain has no biowallet API configured', async () => { - mockGetBiowalletApi.mockReturnValue(null) + describe('successful balance query', () => { + it('returns balance from chain provider', async () => { + mockFetch.mockResolvedValue({ + amount: { toRawString: () => '1000000000' }, + }) const result = await handleGetBalance( - { address: 'b123', chain: 'unknown-chain' }, + { address: 'b123456789', chain: 'bfmeta' }, mockContext ) - expect(result).toBe('0') - expect(mockGetBiowalletApi).toHaveBeenCalledWith('unknown-chain') - expect(mockGetAccountBalance).not.toHaveBeenCalled() + expect(result).toBe('1000000000') + expect(mockGetChainProvider).toHaveBeenCalledWith('bfmeta') + expect(mockFetch).toHaveBeenCalledWith({ address: 'b123456789' }) }) - }) - describe('successful balance query', () => { - it('returns balance from bioforest SDK', async () => { - mockGetBiowalletApi.mockReturnValue('https://walletapi.bfmeta.info/wallet/bfm') - mockGetAccountBalance.mockResolvedValue('1000000000') + it('returns "0" for account with no balance', async () => { + mockFetch.mockResolvedValue({ + amount: { toRawString: () => '0' }, + }) const result = await handleGetBalance( - { address: 'b123456789', chain: 'bfmeta' }, + { address: 'b_new_account', chain: 'bfmeta' }, mockContext ) - expect(result).toBe('1000000000') - expect(mockGetBiowalletApi).toHaveBeenCalledWith('bfmeta') - expect(mockGetAccountBalance).toHaveBeenCalledWith( - 'https://walletapi.bfmeta.info/wallet/bfm', - 'bfmeta', - 'b123456789' - ) + expect(result).toBe('0') }) - it('returns "0" for account with no balance', async () => { - mockGetBiowalletApi.mockReturnValue('https://walletapi.bfmeta.info/wallet/bfm') - mockGetAccountBalance.mockResolvedValue('0') + it('returns "0" when balance is null', async () => { + mockFetch.mockResolvedValue(null) const result = await handleGetBalance( - { address: 'b_new_account', chain: 'bfmeta' }, + { address: 'b123', chain: 'bfmeta' }, mockContext ) @@ -113,10 +111,8 @@ describe('handleGetBalance', () => { }) describe('error handling', () => { - it('returns "0" and logs warning when SDK throws error', async () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - mockGetBiowalletApi.mockReturnValue('https://walletapi.bfmeta.info/wallet/bfm') - mockGetAccountBalance.mockRejectedValue(new Error('Network error')) + it('returns "0" when provider throws error', async () => { + mockFetch.mockRejectedValue(new Error('Network error')) const result = await handleGetBalance( { address: 'b123', chain: 'bfmeta' }, @@ -124,26 +120,47 @@ describe('handleGetBalance', () => { ) expect(result).toBe('0') - expect(consoleWarnSpy).toHaveBeenCalledWith( - '[bio_getBalance] Failed to query balance:', - expect.any(Error) + }) + + it('returns "0" when amount is undefined', async () => { + mockFetch.mockResolvedValue({ amount: undefined }) + + const result = await handleGetBalance( + { address: 'b123', chain: 'bfmeta' }, + mockContext ) - consoleWarnSpy.mockRestore() + expect(result).toBe('0') }) + }) - it('returns "0" when SDK returns undefined', async () => { - mockGetBiowalletApi.mockReturnValue('https://walletapi.bfmeta.info/wallet/bfm') - // @ts-expect-error - testing edge case - mockGetAccountBalance.mockResolvedValue(undefined) + describe('different chains', () => { + it('works with ethereum chain', async () => { + mockFetch.mockResolvedValue({ + amount: { toRawString: () => '5000000000000000000' }, + }) const result = await handleGetBalance( - { address: 'b123', chain: 'bfmeta' }, + { address: '0x1234567890abcdef', chain: 'ethereum' }, + mockContext + ) + + expect(result).toBe('5000000000000000000') + expect(mockGetChainProvider).toHaveBeenCalledWith('ethereum') + }) + + it('works with tron chain', async () => { + mockFetch.mockResolvedValue({ + amount: { toRawString: () => '100000000' }, + }) + + const result = await handleGetBalance( + { address: 'TXyz123', chain: 'tron' }, mockContext ) - // undefined will be returned as-is, but in practice SDK always returns string - expect(result).toBeUndefined() + expect(result).toBe('100000000') + expect(mockGetChainProvider).toHaveBeenCalledWith('tron') }) }) }) diff --git a/src/services/ecosystem/bridge.ts b/src/services/ecosystem/bridge.ts index bd225f1f0..56d7cfbb0 100644 --- a/src/services/ecosystem/bridge.ts +++ b/src/services/ecosystem/bridge.ts @@ -126,7 +126,7 @@ export class PostMessageBridge { this.messageHandler = this.handleMessage.bind(this) window.addEventListener('message', this.messageHandler) - + } /** Detach from iframe */ @@ -170,21 +170,57 @@ export class PostMessageBridge { } private handleMessage(event: MessageEvent): void { - // Validate origin - if (this.origin !== '*' && event.origin !== this.origin) { + const data = event.data as RequestMessage + + // Early exit for non-bio messages that we don't care about + if (!data || typeof data !== 'object' || !('type' in data)) { return } - // Validate source - if (event.source !== this.iframe?.contentWindow) { + // Only process bio/eth/tron requests + const isBioRequest = data.type === 'bio_request' || data.type === 'eth_request' || data.type === 'tron_request' + if (!isBioRequest) { return } - const data = event.data as RequestMessage - if (!data || typeof data !== 'object' || !('type' in data)) { + // Debug: Log bio messages received + console.log('[BioBridge] Received bio message:', { + origin: event.origin, + expectedOrigin: this.origin, + type: data.type, + method: (data as { method?: string }).method, + }) + + // Validate origin + // For development with mixed content (HTTP miniapp in HTTPS host), + // allow localhost with different protocols + const isLocalhostDev = + this.origin.includes('localhost') && + event.origin.includes('localhost') + const originMatches = + this.origin === '*' || + event.origin === this.origin || + isLocalhostDev + + if (!originMatches) { + console.warn('[BioBridge] Origin mismatch, ignoring message') + return + } + + // Validate source - for localhost dev, use relaxed check + // In cross-origin scenarios, both event.source and iframe.contentWindow + // may be opaque Window objects that don't compare as equal + const sourceMatches = + event.source === this.iframe?.contentWindow || + (isLocalhostDev && event.source instanceof Window) + + if (!sourceMatches) { + console.warn('[BioBridge] Source mismatch, ignoring message') return } + console.log('[BioBridge] Processing message:', data.type) + // Route to appropriate handler based on protocol if (data.type === 'bio_request') { this.processRequest(data, 'bio') @@ -195,10 +231,12 @@ export class PostMessageBridge { } } + + private async processRequest(request: RequestMessage, protocol: Protocol): Promise { const { id, method, params } = request - + // Check if handler exists const handler = this.handlers.get(method) @@ -289,7 +327,7 @@ export class PostMessageBridge { /** 请求用户授权权限 */ private async requestPermission(permissions: string[]): Promise { if (!this.permissionRequestCallback) { - + return false } @@ -304,7 +342,7 @@ export class PostMessageBridge { } return approved } catch (error) { - + return false } } @@ -312,7 +350,7 @@ export class PostMessageBridge { private sendResponse(_protocol: Protocol, response: BioResponseMessage | EthResponseMessage | TronResponseMessage): void { if (!this.iframe?.contentWindow) return - + this.iframe.contentWindow.postMessage(response, this.origin) } } diff --git a/src/services/ecosystem/handlers/crypto.ts b/src/services/ecosystem/handlers/crypto.ts new file mode 100644 index 000000000..810b36206 --- /dev/null +++ b/src/services/ecosystem/handlers/crypto.ts @@ -0,0 +1,184 @@ +/** + * Crypto 黑盒 BioBridge Handler + * + * 处理 bio_requestCryptoToken 和 bio_cryptoExecute 请求 + */ + +import type { MethodHandler } from '../types' +import { tokenStore, cryptoExecutor, CryptoBoxErrorCodes } from '../../crypto-box' +import type { + RequestCryptoTokenParams, + RequestCryptoTokenResponse, + CryptoExecuteParams, + GetCryptoTokenInfoParams, + GetCryptoTokenInfoResponse, + CryptoAction, + TokenDuration, +} from '../../crypto-box' + +// 获取授权对话框的注入器 +let getCryptoAuthorizeDialog: ((appId: string) => ((params: { + actions: CryptoAction[] + duration: TokenDuration + address: string + chainId?: string + app: { name: string; icon?: string } +}) => Promise<{ approved: boolean; patternKey?: string; walletId?: string; selectedDuration?: string }>) | null) | null = null + +/** + * 注册 Crypto 授权对话框获取器 + */ +export function setCryptoAuthorizeDialog( + getter: ((appId: string) => ((params: { + actions: CryptoAction[] + duration: TokenDuration + address: string + chainId?: string + app: { name: string; icon?: string } + }) => Promise<{ approved: boolean; patternKey?: string; walletId?: string; selectedDuration?: string }>) | null) | null +): void { + getCryptoAuthorizeDialog = getter +} + +/** + * bio_requestCryptoToken - 请求加密操作授权 + * + * 用户输入手势密码后生成 Token + */ +export const handleRequestCryptoToken: MethodHandler = async (params, context) => { + const opts = params as RequestCryptoTokenParams | undefined + + if (!opts?.actions || !opts?.duration || !opts?.address) { + throw Object.assign( + new Error('Missing required parameters: actions, duration, address'), + { code: CryptoBoxErrorCodes.INTERNAL_ERROR } + ) + } + + // 验证 actions + const validActions: CryptoAction[] = ['asymmetricEncrypt', 'sign'] + for (const action of opts.actions) { + if (!validActions.includes(action)) { + throw Object.assign( + new Error(`Invalid action: ${action}`), + { code: CryptoBoxErrorCodes.INTERNAL_ERROR } + ) + } + } + + // 获取授权对话框 + if (!getCryptoAuthorizeDialog) { + throw Object.assign( + new Error('Crypto authorize dialog not available'), + { code: CryptoBoxErrorCodes.INTERNAL_ERROR } + ) + } + + const showDialog = getCryptoAuthorizeDialog(context.appId) + if (!showDialog) { + throw Object.assign( + new Error('Crypto authorize dialog not available'), + { code: CryptoBoxErrorCodes.INTERNAL_ERROR } + ) + } + + // 显示授权对话框,等待用户输入手势密码 + const result = await showDialog({ + actions: opts.actions, + duration: opts.duration, + address: opts.address, + chainId: opts.chainId, + app: { name: context.appName, icon: context.appIcon }, + }) + + if (!result.approved || !result.patternKey || !result.walletId) { + throw Object.assign( + new Error('User rejected'), + { code: CryptoBoxErrorCodes.USER_REJECTED } + ) + } + + // 使用用户选择的时长(如果有),否则使用默认请求时长 + const finalDuration = (result.selectedDuration as TokenDuration) || opts.duration + + // 确保 TokenStore 已初始化 + await tokenStore.initialize() + + // 创建 Token(返回 token 和 sessionSecret) + const { token, sessionSecret } = await tokenStore.createToken({ + miniappId: context.appId, + walletId: result.walletId, + address: opts.address, + actions: opts.actions, + duration: finalDuration, + patternKey: result.patternKey, + }) + + const response: RequestCryptoTokenResponse = { + tokenId: token.tokenId, + sessionSecret, + expiresAt: token.expiresAt, + grantedActions: token.actions, + address: token.address, // 返回 Token 绑定的地址,miniapp 应缓存此地址 + } + + return response +} + +/** + * bio_cryptoExecute - 使用 Token 执行加密操作 + */ +export const handleCryptoExecute: MethodHandler = async (params, context) => { + const opts = params as CryptoExecuteParams | undefined + + if (!opts?.tokenId || !opts?.sessionSecret || !opts?.action || !opts?.params) { + throw Object.assign( + new Error('Missing required parameters: tokenId, sessionSecret, action, params'), + { code: CryptoBoxErrorCodes.INTERNAL_ERROR } + ) + } + + // 确保 TokenStore 已初始化 + await tokenStore.initialize() + + // 执行加密操作(sessionSecret 用于解密 Payload) + const result = await cryptoExecutor.execute(opts, context.appId) + + return result +} + +/** + * bio_getCryptoTokenInfo - 查询 Token 信息 + * + * 用于 miniapp 检查缓存的 Token 是否有效,以及获取 Token 绑定的地址 + */ +export const handleGetCryptoTokenInfo: MethodHandler = async (params, context) => { + const opts = params as GetCryptoTokenInfoParams | undefined + + if (!opts?.tokenId || !opts?.sessionSecret) { + throw Object.assign( + new Error('Missing required parameters: tokenId, sessionSecret'), + { code: CryptoBoxErrorCodes.INTERNAL_ERROR } + ) + } + + // 确保 TokenStore 已初始化 + await tokenStore.initialize() + + // 查询 Token 信息 + const info = await tokenStore.getTokenInfo( + opts.tokenId, + opts.sessionSecret, + context.appId + ) + + const response: GetCryptoTokenInfoResponse = { + valid: info.valid, + address: info.address, + expiresAt: info.expiresAt, + actions: info.actions, + invalidReason: info.invalidReason, + } + + return response +} diff --git a/src/services/ecosystem/handlers/wallet.ts b/src/services/ecosystem/handlers/wallet.ts index b7236dfe3..916808fe0 100644 --- a/src/services/ecosystem/handlers/wallet.ts +++ b/src/services/ecosystem/handlers/wallet.ts @@ -40,13 +40,17 @@ export const handleConnect: MethodHandler = async (_params, _context) => { } /** bio_requestAccounts - Request wallet connection (shows UI) */ -export const handleRequestAccounts: MethodHandler = async (_params, context) => { +export const handleRequestAccounts: MethodHandler = async (params, context) => { const showWalletPicker = getWalletPicker(context.appId) if (!showWalletPicker) { throw Object.assign(new Error('Wallet picker not available'), { code: BioErrorCodes.INTERNAL_ERROR }) } + // 支持 chain 参数过滤钱包 + const opts = params as { chain?: string } | undefined + const wallet = await showWalletPicker({ + chain: opts?.chain, app: { name: context.appName, icon: context.appIcon }, }) if (!wallet) { diff --git a/src/services/ecosystem/provider.ts b/src/services/ecosystem/provider.ts index 896e58a8c..5cd588645 100644 --- a/src/services/ecosystem/provider.ts +++ b/src/services/ecosystem/provider.ts @@ -23,6 +23,11 @@ import { registerEvmHandlers, registerTronHandlers, } from './handlers' +import { + handleRequestCryptoToken, + handleCryptoExecute, + handleGetCryptoTokenInfo, +} from './handlers/crypto' /** Track if handlers have been registered */ let initialized = false @@ -57,13 +62,18 @@ export function initBioProvider(): void { // Destroy asset (BioForest chains only) bridge.registerHandler('bio_destroyAsset', handleDestroyAsset) + // Crypto box (Token-based crypto operations) + bridge.registerHandler('bio_requestCryptoToken', handleRequestCryptoToken) + bridge.registerHandler('bio_cryptoExecute', handleCryptoExecute) + bridge.registerHandler('bio_getCryptoTokenInfo', handleGetCryptoTokenInfo) + // EVM methods (Ethereum/BSC via window.ethereum) registerEvmHandlers((method, handler) => bridge.registerHandler(method, handler)) // TRON methods (via window.tronLink/tronWeb) registerTronHandlers((method, handler) => bridge.registerHandler(method, handler)) - + } // Auto-initialize handlers at module load time to prevent race conditions @@ -92,3 +102,6 @@ export { setTronWalletPicker, setTronSigningDialog, } from './handlers' + +// Crypto box setters +export { setCryptoAuthorizeDialog } from './handlers/crypto' diff --git a/src/services/ecosystem/types.ts b/src/services/ecosystem/types.ts index 63d5c410f..87bc6759f 100644 --- a/src/services/ecosystem/types.ts +++ b/src/services/ecosystem/types.ts @@ -29,6 +29,8 @@ export interface BioAccount { address: string chain: string name?: string + /** Public key (optional, for dweb-compat) */ + publicKey?: string } /** @@ -157,6 +159,24 @@ export const KNOWN_PERMISSIONS: Record = { description: '请求销毁资产(需要您确认,不可撤销)', risk: 'high', }, + bio_requestCryptoToken: { + id: 'bio_requestCryptoToken', + name: '请求加密授权', + description: '请求授权进行加密操作(需要您确认)', + risk: 'high', + }, + bio_cryptoExecute: { + id: 'bio_cryptoExecute', + name: '执行加密操作', + description: '使用已授权的 Token 执行加密操作', + risk: 'low', + }, + bio_getCryptoTokenInfo: { + id: 'bio_getCryptoTokenInfo', + name: '查询授权信息', + description: '查询已授权 Token 的信息', + risk: 'low', + }, } /** Miniapp manifest - 完整的小程序元数据 */ diff --git a/src/stackflow/activities/MainTabsActivity.tsx b/src/stackflow/activities/MainTabsActivity.tsx index 778f7d6e6..ecbc778b4 100644 --- a/src/stackflow/activities/MainTabsActivity.tsx +++ b/src/stackflow/activities/MainTabsActivity.tsx @@ -12,6 +12,7 @@ import { getBridge, initBioProvider, setChainSwitchConfirm, + setCryptoAuthorizeDialog, setEvmSigningDialog, setEvmTransactionDialog, setEvmWalletPicker, @@ -340,6 +341,35 @@ export const MainTabsActivity: ActivityComponentType = ({ params }); }); + // Crypto 黑盒授权对话框 + setCryptoAuthorizeDialog((_appId: string) => async (params) => { + return new Promise<{ approved: boolean; patternKey?: string; walletId?: string; selectedDuration?: string }>((resolve) => { + const timeout = window.setTimeout(() => resolve({ approved: false }), 60_000); + + const handleResult = (e: Event) => { + window.clearTimeout(timeout); + const detail = (e as CustomEvent).detail as + | { approved?: boolean; patternKey?: string; walletId?: string; selectedDuration?: string } + | undefined; + if (detail?.approved && detail.patternKey && detail.walletId) { + resolve({ approved: true, patternKey: detail.patternKey, walletId: detail.walletId, selectedDuration: detail.selectedDuration }); + return; + } + resolve({ approved: false }); + }; + + window.addEventListener("crypto-authorize-confirm", handleResult, { once: true }); + push("CryptoAuthorizeJob", { + actions: JSON.stringify(params.actions), + duration: params.duration, + address: params.address, + chainId: params.chainId, + appName: params.app.name, + appIcon: params.app.icon, + }); + }); + }); + return () => { getBridge().setPermissionRequestCallback(null); setWalletPicker(null); @@ -348,6 +378,7 @@ export const MainTabsActivity: ActivityComponentType = ({ params setEvmSigningDialog(null); setEvmTransactionDialog(null); setTronWalletPicker(null); + setCryptoAuthorizeDialog(null); setGetAccounts(null); setSigningDialog(null); setTransferDialog(null); diff --git a/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx b/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx index 9dc2dd964..cc56ff4c6 100644 --- a/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx +++ b/src/stackflow/activities/sheets/ChainSwitchConfirmJob.tsx @@ -11,7 +11,8 @@ import { ChainIcon } from '@/components/wallet/chain-icon' import { MiniappSheetHeader } from '@/components/ecosystem' import { useFlow } from '../../stackflow' import { ActivityParamsProvider, useActivityParams } from '../../hooks' -import { parseHexChainId, getKeyAppChainId, CHAIN_DISPLAY_NAMES } from '@biochain/bio-sdk' +import { parseHexChainId, getKeyAppChainId } from '@biochain/bio-sdk' +import { chainConfigService } from '@/services/chain-config' import type { ChainType } from '@/stores' type ChainSwitchConfirmJobParams = { @@ -28,8 +29,8 @@ type ChainSwitchConfirmJobParams = { /** 获取链的显示名称 */ function getChainDisplayName(hexChainId: string): string { const keyAppId = getKeyAppChainId(hexChainId) - if (keyAppId && CHAIN_DISPLAY_NAMES[keyAppId]) { - return CHAIN_DISPLAY_NAMES[keyAppId] + if (keyAppId) { + return chainConfigService.getName(keyAppId) } // Fallback: 显示 decimal chainId try { diff --git a/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx b/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx new file mode 100644 index 000000000..1a7af9586 --- /dev/null +++ b/src/stackflow/activities/sheets/CryptoAuthorizeJob.tsx @@ -0,0 +1,234 @@ +/** + * CryptoAuthorizeJob - Crypto 黑盒授权对话框 + * + * 用于小程序请求加密操作授权,用户需输入手势密码确认 + */ + +import { useCallback, useState } from 'react' +import type { ActivityComponentType } from '@stackflow/react' +import { BottomSheet } from '@/components/layout/bottom-sheet' +import { useTranslation } from 'react-i18next' +import { IconLock, IconLoader2 } from '@tabler/icons-react' +import { useFlow } from '../../stackflow' +import { ActivityParamsProvider, useActivityParams } from '../../hooks' +import { MiniappSheetHeader } from '@/components/ecosystem' +import { PatternLock, patternToString } from '@/components/security/pattern-lock' +import { walletStorageService } from '@/services/wallet-storage' +import { + type CryptoAction, + type TokenDuration, + CRYPTO_ACTION_LABELS, + TOKEN_DURATION_LABELS, + TOKEN_DURATION_OPTIONS, +} from '@/services/crypto-box' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { walletStore } from '@/stores' + +type CryptoAuthorizeJobParams = { + /** 请求的操作权限 (JSON 字符串) */ + actions: string + /** 授权时长 */ + duration: string + /** 使用的地址 */ + address: string + /** 链 ID */ + chainId?: string + /** 请求来源小程序名称 */ + appName?: string + /** 请求来源小程序图标 */ + appIcon?: string +} + +function CryptoAuthorizeJobContent() { + const { t } = useTranslation('common') + const { pop } = useFlow() + const params = useActivityParams() + + const actions = JSON.parse(params.actions) as CryptoAction[] + const duration = params.duration as TokenDuration + const { address, chainId, appName, appIcon } = params + + // 找到使用该地址的钱包 + const targetWallet = walletStore.state.wallets.find( + w => w.chainAddresses.some(ca => ca.address === address) + ) + const walletName = targetWallet?.name + const walletId = targetWallet?.id + + const [pattern, setPattern] = useState([]) + const [error, setError] = useState(false) + const [isVerifying, setIsVerifying] = useState(false) + const [selectedDuration, setSelectedDuration] = useState(duration) + + const handlePatternComplete = useCallback( + async (nodes: number[]) => { + setIsVerifying(true) + setError(false) + + try { + const patternKey = patternToString(nodes) + + // 验证手势密码是否正确 + const wallets = await walletStorageService.getAllWallets() + if (wallets.length === 0) { + setError(true) + setPattern([]) + setIsVerifying(false) + return + } + + let isValid = false + for (const wallet of wallets) { + try { + await walletStorageService.getMnemonic(wallet.id, patternKey) + isValid = true + break + } catch { + // 继续尝试下一个钱包 + } + } + + if (isValid && walletId) { + // 发送成功事件(包含 walletId 和 selectedDuration 用于 Token 创建) + const event = new CustomEvent('crypto-authorize-confirm', { + detail: { approved: true, patternKey, walletId, selectedDuration }, + }) + window.dispatchEvent(event) + pop() + } else { + setError(true) + setPattern([]) + } + } catch { + setError(true) + setPattern([]) + } finally { + setIsVerifying(false) + } + }, + [pop, selectedDuration] + ) + + const handleCancel = useCallback(() => { + const event = new CustomEvent('crypto-authorize-confirm', { + detail: { approved: false }, + }) + window.dispatchEvent(event) + pop() + }, [pop]) + + return ( + +
+ {/* Handle */} +
+
+
+ + {/* Header - 左侧 miniapp 信息,右侧钱包信息 */} + + + {/* Content - 可滚动区域 */} +
+ {/* 权限和授权时长 */} +
+ {/* 请求权限 - 水平排列 */} +
+ + {t('permissions', '权限')}: + + {actions.map(a => CRYPTO_ACTION_LABELS[a]?.name || a).join('、')} + +
+ + {/* 授权时长 */} +
+ {t('duration', '时长')}: + +
+
+
+ + {/* 手势密码区域 - 固定尺寸不可压缩 */} +
+

+ {t('drawPatternToConfirm', '请绘制手势密码确认')} +

+ {/* 固定尺寸容器,防止内容变化导致布局抖动 */} +
+
+ +
+
+ {/* 验证中状态 - 固定高度 */} +
+ {isVerifying && ( +
+ + {t('verifying', '验证中...')} +
+ )} +
+
+ + {/* Cancel button */} +
+ +
+ + {/* Safe area */} +
+
+ + ) +} + +export const CryptoAuthorizeJob: ActivityComponentType = ({ params }) => { + return ( + + + + ) +} diff --git a/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx b/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx index cb15f1fa2..48ed2e7ab 100644 --- a/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx +++ b/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx @@ -12,12 +12,13 @@ import { IconFlame, IconAlertTriangle, IconLoader2 } from '@tabler/icons-react' import { useFlow } from '../../stackflow' import { ActivityParamsProvider, useActivityParams } from '../../hooks' import { setWalletLockConfirmCallback } from './WalletLockConfirmJob' -import { useCurrentWallet, useChainConfigState, chainConfigSelectors } from '@/stores' +import { useCurrentWallet, useChainConfigState, chainConfigSelectors, walletStore } from '@/stores' import { submitBioforestBurn } from '@/hooks/use-burn.bioforest' import { Amount } from '@/types/amount' -import { AddressDisplay } from '@/components/wallet/address-display' import { AmountDisplay } from '@/components/common/amount-display' import { MiniappSheetHeader } from '@/components/ecosystem' +import { ChainBadge } from '@/components/wallet/chain-icon' +import { ChainAddressDisplay } from '@/components/wallet/chain-address-display' type MiniappDestroyConfirmJobParams = { /** 来源小程序名称 */ @@ -48,6 +49,12 @@ function MiniappDestroyConfirmJobContent() { const [isConfirming, setIsConfirming] = useState(false) + // 查找使用该地址的钱包 + const targetWallet = walletStore.state.wallets.find( + w => w.chainAddresses.some(ca => ca.address === from) + ) + const walletName = targetWallet?.name || t('common:unknownWallet', '未知钱包') + const handleConfirm = useCallback(() => { if (isConfirming) return @@ -139,6 +146,11 @@ function MiniappDestroyConfirmJobContent() { description={`${appName || t('common:unknownDApp', 'Unknown App')} ${t('common:requestsDestroy', '请求销毁资产')}`} appName={appName} appIcon={appIcon} + walletInfo={{ + name: walletName, + address: from, + chainId: chain, + }} /> {/* Content */} @@ -161,7 +173,7 @@ function MiniappDestroyConfirmJobContent() { {t('common:from', '来自')} - +
@@ -170,7 +182,7 @@ function MiniappDestroyConfirmJobContent() { {t('common:network', '网络')} - {chain} +
{/* Warning */} diff --git a/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx b/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx index e899bfe8e..771fad7fe 100644 --- a/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx +++ b/src/stackflow/activities/sheets/MiniappSignTransactionJob.tsx @@ -16,6 +16,8 @@ import { walletStore } from '@/stores' import type { UnsignedTransaction } from '@/services/ecosystem' import { signUnsignedTransaction } from '@/services/ecosystem/handlers' import { MiniappSheetHeader } from '@/components/ecosystem' +import { ChainBadge } from '@/components/wallet/chain-icon' +import { ChainAddressDisplay } from '@/components/wallet/chain-address-display' type MiniappSignTransactionJobParams = { /** 来源小程序名称 */ @@ -65,6 +67,10 @@ function MiniappSignTransactionJobContent() { return findWalletIdByAddress(chain, from) }, [chain, from]) + // 查找钱包名称 + const targetWallet = walletStore.state.wallets.find(w => w.id === walletId) + const walletName = targetWallet?.name || t('unknownWallet', '未知钱包') + const handleConfirm = useCallback(() => { if (isSubmitting) return if (!unsignedTx) return @@ -133,6 +139,11 @@ function MiniappSignTransactionJobContent() { description={appName || t('unknownDApp', '未知 DApp')} appName={appName} appIcon={appIcon} + walletInfo={{ + name: walletName, + address: from, + chainId: chain, + }} />
@@ -150,12 +161,12 @@ function MiniappSignTransactionJobContent() {

{t('network', '网络')}

-

{chain}

+

{t('signingAddress', '签名地址')}

-

{from}

+
diff --git a/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx b/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx index 5703b23ee..aa9d9b115 100644 --- a/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx +++ b/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx @@ -12,11 +12,12 @@ import { IconArrowDown, IconAlertTriangle, IconLoader2 } from '@tabler/icons-rea import { useFlow } from '../../stackflow' import { ActivityParamsProvider, useActivityParams } from '../../hooks' import { setWalletLockConfirmCallback } from './WalletLockConfirmJob' -import { useCurrentWallet } from '@/stores' +import { useCurrentWallet, walletStore } from '@/stores' import { SignatureAuthService, plaocAdapter } from '@/services/authorize' import { AddressDisplay } from '@/components/wallet/address-display' import { AmountDisplay } from '@/components/common/amount-display' import { MiniappSheetHeader } from '@/components/ecosystem' +import { ChainBadge } from '@/components/wallet/chain-icon' type MiniappTransferConfirmJobParams = { /** 来源小程序名称 */ @@ -44,17 +45,23 @@ function MiniappTransferConfirmJobContent() { const [isConfirming, setIsConfirming] = useState(false) + // 查找使用该地址的钱包 + const targetWallet = walletStore.state.wallets.find( + w => w.chainAddresses.some(ca => ca.address === from) + ) + const walletName = targetWallet?.name || t('unknownWallet', '未知钱包') + const handleConfirm = useCallback(() => { if (isConfirming) return // 设置钱包锁验证回调 setWalletLockConfirmCallback(async (password: string) => { setIsConfirming(true) - + try { const encryptedSecret = currentWallet?.encryptedMnemonic if (!encryptedSecret) { - + return false } @@ -78,7 +85,7 @@ function MiniappTransferConfirmJobContent() { if (asset) { transferPayload.assetType = asset } - + const signature = await authService.handleTransferSign( transferPayload, encryptedSecret, @@ -97,11 +104,11 @@ function MiniappTransferConfirmJobContent() { }, }) window.dispatchEvent(event) - + pop() return true } catch (error) { - + return false } finally { setIsConfirming(false) @@ -138,16 +145,21 @@ function MiniappTransferConfirmJobContent() { description={`${appName || t('unknownDApp', '未知 DApp')} ${t('requestsTransfer', '请求发送转账')}`} appName={appName} appIcon={appIcon} + walletInfo={{ + name: walletName, + address: from, + chainId: chain, + }} /> {/* Content */}
{/* Amount */}
- {t('network', '网络')} - {chain} +
{/* Warning */} diff --git a/src/stackflow/activities/sheets/SigningConfirmJob.tsx b/src/stackflow/activities/sheets/SigningConfirmJob.tsx index 887a96bea..e60cebe5b 100644 --- a/src/stackflow/activities/sheets/SigningConfirmJob.tsx +++ b/src/stackflow/activities/sheets/SigningConfirmJob.tsx @@ -12,10 +12,9 @@ import { IconAlertTriangle, IconLoader2 } from '@tabler/icons-react' import { useFlow } from '../../stackflow' import { ActivityParamsProvider, useActivityParams } from '../../hooks' import { setWalletLockConfirmCallback } from './WalletLockConfirmJob' -import { useCurrentWallet } from '@/stores' +import { useCurrentWallet, walletStore } from '@/stores' import { SignatureAuthService, plaocAdapter } from '@/services/authorize' import { MiniappSheetHeader } from '@/components/ecosystem' -import { AddressDisplay } from '@/components/wallet/address-display' type SigningConfirmJobParams = { /** 要签名的消息 */ @@ -37,17 +36,23 @@ function SigningConfirmJobContent() { const currentWallet = useCurrentWallet() const [isSubmitting, setIsSubmitting] = useState(false) + // 查找使用该地址的钱包 + const targetWallet = walletStore.state.wallets.find( + w => w.chainAddresses.some(ca => ca.address === address) + ) + const walletName = targetWallet?.name || t('unknownWallet', '未知钱包') + const handleConfirm = useCallback(() => { if (isSubmitting) return // 设置钱包锁验证回调 setWalletLockConfirmCallback(async (password: string) => { setIsSubmitting(true) - + try { const encryptedSecret = currentWallet?.encryptedMnemonic if (!encryptedSecret) { - + return false } @@ -68,18 +73,18 @@ function SigningConfirmJobContent() { // 发送成功事件(包含 signature 和 publicKey) const event = new CustomEvent('signing-confirm', { - detail: { - confirmed: true, + detail: { + confirmed: true, signature: signResult.signature, publicKey: signResult.publicKey, }, }) window.dispatchEvent(event) - + pop() return true } catch (error) { - + return false } finally { setIsSubmitting(false) @@ -117,18 +122,15 @@ function SigningConfirmJobContent() { description={appName || t('unknownDApp', '未知 DApp')} appName={appName} appIcon={appIcon} + walletInfo={{ + name: walletName, + address, + chainId: chainName || 'bfmeta', + }} /> {/* Content */}
- {/* Address */} -
-

- {t('signingAddress', '签名地址')} -

- -
- {/* Message */}

diff --git a/src/stackflow/activities/sheets/WalletPickerJob.tsx b/src/stackflow/activities/sheets/WalletPickerJob.tsx index c82387ddc..90d0a92e3 100644 --- a/src/stackflow/activities/sheets/WalletPickerJob.tsx +++ b/src/stackflow/activities/sheets/WalletPickerJob.tsx @@ -13,7 +13,9 @@ import { useFlow } from '../../stackflow' import { ActivityParamsProvider, useActivityParams } from '../../hooks' import { WalletList, type WalletListItem } from '@/components/wallet/wallet-list' import { MiniappSheetHeader } from '@/components/ecosystem' -import { getKeyAppChainId, normalizeChainId, CHAIN_DISPLAY_NAMES } from '@biochain/bio-sdk' +import { getKeyAppChainId, normalizeChainId } from '@biochain/bio-sdk' +import { useChainConfigs } from '@/stores/chain-config' +import { chainConfigService } from '@/services/chain-config' type WalletPickerJobParams = { /** 限定链类型 (支持: KeyApp 内部 ID, EVM hex chainId, API 名称如 BSC) */ @@ -45,8 +47,8 @@ function resolveChainId(chain: string | undefined): string | undefined { // Try API name normalization (e.g., 'BSC' -> 'binance', 'BFMCHAIN' -> 'bfmeta') const normalized = normalizeChainId(chain) - // Check if it's a known chain - if (CHAIN_DISPLAY_NAMES[normalized]) { + // Check if it's a known chain (using chainConfigService) + if (chainConfigService.getConfig(normalized)) { return normalized } @@ -61,10 +63,18 @@ function WalletPickerJobContent() { const { t } = useTranslation('common') const { pop } = useFlow() const { chain: rawChain, exclude, appName, appIcon } = useActivityParams() + const chainConfigs = useChainConfigs() // Resolve chain to KeyApp internal ID const chain = useMemo(() => resolveChainId(rawChain), [rawChain]) + // 获取链配置(用于显示名称和图标) + const chainConfig = useMemo( + () => chain ? chainConfigs.find(c => c.id === chain) : null, + [chain, chainConfigs] + ) + const chainDisplayName = chain ? chainConfigService.getName(chain) : undefined + const walletState = useStore(walletStore) const currentWallet = walletSelectors.getCurrentWallet(walletState) @@ -90,7 +100,7 @@ function WalletPickerJobContent() { name: wallet.name, address: chainAddress.address, themeHue: wallet.themeHue, - chainIconUrl: undefined, // TODO: 从链配置获取图标 + chainIconUrl: chainConfig?.icon, }) } @@ -143,10 +153,11 @@ function WalletPickerJobContent() { {/* Title with App Icon */} {/* Wallet List */} diff --git a/src/stackflow/activities/sheets/index.ts b/src/stackflow/activities/sheets/index.ts index b7040f658..a45fcce4f 100644 --- a/src/stackflow/activities/sheets/index.ts +++ b/src/stackflow/activities/sheets/index.ts @@ -24,3 +24,4 @@ export { MiniappTransferConfirmJob } from "./MiniappTransferConfirmJob"; export { MiniappDestroyConfirmJob } from "./MiniappDestroyConfirmJob"; export { MiniappSignTransactionJob } from "./MiniappSignTransactionJob"; export { ChainSwitchConfirmJob } from "./ChainSwitchConfirmJob"; +export { CryptoAuthorizeJob } from "./CryptoAuthorizeJob"; diff --git a/src/stackflow/hooks/use-navigation.ts b/src/stackflow/hooks/use-navigation.ts index 54cf3c684..d8e61709a 100644 --- a/src/stackflow/hooks/use-navigation.ts +++ b/src/stackflow/hooks/use-navigation.ts @@ -16,6 +16,7 @@ const routeToActivityMap: Record = { "/settings/wallet-lock": "SettingsWalletLockActivity", "/settings/wallet-chains": "SettingsWalletChainsActivity", "/settings/storage": "SettingsStorageActivity", + "/settings/sources": "SettingsSourcesActivity", "/history": "HistoryActivity", "/scanner": "ScannerActivity", "/onboarding/recover": "OnboardingRecoverActivity", diff --git a/src/stackflow/stackflow.ts b/src/stackflow/stackflow.ts index 11de79d2c..e00580b12 100644 --- a/src/stackflow/stackflow.ts +++ b/src/stackflow/stackflow.ts @@ -62,6 +62,7 @@ import { MiniappDestroyConfirmJob, MiniappSignTransactionJob, ChainSwitchConfirmJob, + CryptoAuthorizeJob, } from './activities/sheets'; export const { Stack, useFlow, useStepFlow, activities } = stackflow({ @@ -130,6 +131,7 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ MiniappDestroyConfirmJob: '/job/miniapp-destroy-confirm', MiniappSignTransactionJob: '/job/miniapp-sign-transaction', ChainSwitchConfirmJob: '/job/chain-switch-confirm', + CryptoAuthorizeJob: '/job/crypto-authorize', }, fallbackActivity: () => 'MainTabsActivity', useHash: true, @@ -197,6 +199,7 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ MiniappDestroyConfirmJob, MiniappSignTransactionJob, ChainSwitchConfirmJob, + CryptoAuthorizeJob, }, // Note: Don't set initialActivity when using historySyncPlugin // The plugin will determine the initial activity based on the URL diff --git a/test-report.html b/test-report.html new file mode 100644 index 000000000..3f78a56f7 --- /dev/null +++ b/test-report.html @@ -0,0 +1,183 @@ + + + + + + CryptoBox 测试报告 + + + +

+
+

🔐 CryptoBox 测试报告

+

Crypto 黑盒 Token 授权系统 - 单元测试

+
+ +
+
+
37
+
总测试数
+
+
+
37
+
通过
+
+
+
0
+
失败
+
+
+ +
+
+
📦
+
TokenStore
+
21 tests
+
+
+
initialize › should initialize without error3.4ms
+
initialize › should be idempotent0.3ms
+
generateTokenId › should generate a valid UUID0.7ms
+
generateTokenId › should generate unique IDs0.6ms
+
hashPattern › should generate SHA-256 hash1.4ms
+
hashPattern › should generate consistent hash0.3ms
+
hashPattern › should generate different hashes0.3ms
+
createToken › should create token with correct properties1.9ms
+
createToken › should set correct expiry for 5min0.8ms
+
createToken › should not include patternHash0.5ms
+
getToken › should retrieve a stored token0.5ms
+
getToken › should return null for non-existent0.2ms
+
deleteToken › should delete a token0.6ms
+
deleteToken › should not throw for non-existent0.3ms
+
validateToken › should return valid for correct token0.3ms
+
validateToken › should return TOKEN_NOT_FOUND0.2ms
+
validateToken › should return MINIAPP_MISMATCH0.4ms
+
validateToken › should return TOKEN_EXPIRED1.0ms
+
validateToken › should return ACTION_NOT_PERMITTED0.4ms
+
getTokensByMiniapp › should return all tokens0.9ms
+
getTokensByMiniapp › should return empty array0.2ms
+
+
+ +
+
+
📐
+
CryptoBox Types
+
16 tests
+
+
+
TOKEN_DURATION_MS › 5min = 300000ms0.6ms
+
TOKEN_DURATION_MS › 15min = 900000ms0.1ms
+
TOKEN_DURATION_MS › 1hour = 3600000ms0.1ms
+
TOKEN_DURATION_MS › 1day = 86400000ms0.1ms
+
CryptoBoxErrorCodes › TOKEN_NOT_FOUND = 41000.1ms
+
CryptoBoxErrorCodes › MINIAPP_MISMATCH = 41010.1ms
+
CryptoBoxErrorCodes › TOKEN_EXPIRED = 41020.1ms
+
CryptoBoxErrorCodes › ACTION_NOT_PERMITTED = 41030.1ms
+
CryptoBoxErrorCodes › USER_REJECTED = 40010.1ms
+
Type guards › valid CryptoAction values0.5ms
+
Type guards › valid TokenDuration values0.1ms
+
Type guards › valid CryptoToken0.4ms
+
Type guards › valid StoredToken with patternHash0.1ms
+
Type guards › valid RequestCryptoTokenParams0.1ms
+
Type guards › valid CryptoExecuteParams asymmetricEncrypt0.1ms
+
Type guards › valid CryptoExecuteParams sign0.1ms
+
+
+ + +
+ + diff --git a/test-results.json b/test-results.json new file mode 100644 index 000000000..8357c51b3 --- /dev/null +++ b/test-results.json @@ -0,0 +1 @@ +{"numTotalTestSuites":15,"numPassedTestSuites":15,"numFailedTestSuites":0,"numPendingTestSuites":0,"numTotalTests":37,"numPassedTests":37,"numFailedTests":0,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1768719958315,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["TokenStore","initialize"],"fullName":"TokenStore initialize should initialize without error","status":"passed","title":"should initialize without error","duration":3.3858339999999316,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","initialize"],"fullName":"TokenStore initialize should be idempotent","status":"passed","title":"should be idempotent","duration":0.3192500000000109,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","generateTokenId"],"fullName":"TokenStore generateTokenId should generate a valid UUID","status":"passed","title":"should generate a valid UUID","duration":0.7149580000000242,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","generateTokenId"],"fullName":"TokenStore generateTokenId should generate unique IDs","status":"passed","title":"should generate unique IDs","duration":0.6206250000000182,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","hashPattern"],"fullName":"TokenStore hashPattern should generate SHA-256 hash","status":"passed","title":"should generate SHA-256 hash","duration":1.4331669999999122,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","hashPattern"],"fullName":"TokenStore hashPattern should generate consistent hash for same input","status":"passed","title":"should generate consistent hash for same input","duration":0.27529200000003584,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","hashPattern"],"fullName":"TokenStore hashPattern should generate different hashes for different inputs","status":"passed","title":"should generate different hashes for different inputs","duration":0.2883750000000873,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","createToken"],"fullName":"TokenStore createToken should create a token with correct properties","status":"passed","title":"should create a token with correct properties","duration":1.8577499999998963,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","createToken"],"fullName":"TokenStore createToken should set correct expiry for 5min duration","status":"passed","title":"should set correct expiry for 5min duration","duration":0.8224170000000868,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","createToken"],"fullName":"TokenStore createToken should not include patternHash in returned token","status":"passed","title":"should not include patternHash in returned token","duration":0.5428750000000946,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","getToken"],"fullName":"TokenStore getToken should retrieve a stored token","status":"passed","title":"should retrieve a stored token","duration":0.5220409999999447,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","getToken"],"fullName":"TokenStore getToken should return null for non-existent token","status":"passed","title":"should return null for non-existent token","duration":0.230040999999801,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","deleteToken"],"fullName":"TokenStore deleteToken should delete a token","status":"passed","title":"should delete a token","duration":0.6250839999997879,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","deleteToken"],"fullName":"TokenStore deleteToken should not throw for non-existent token","status":"passed","title":"should not throw for non-existent token","duration":0.28637499999990723,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","validateToken"],"fullName":"TokenStore validateToken should return valid for correct token and action","status":"passed","title":"should return valid for correct token and action","duration":0.34883300000001327,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","validateToken"],"fullName":"TokenStore validateToken should return TOKEN_NOT_FOUND for non-existent token","status":"passed","title":"should return TOKEN_NOT_FOUND for non-existent token","duration":0.1949170000000322,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","validateToken"],"fullName":"TokenStore validateToken should return MINIAPP_MISMATCH for wrong miniappId","status":"passed","title":"should return MINIAPP_MISMATCH for wrong miniappId","duration":0.36487499999998363,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","validateToken"],"fullName":"TokenStore validateToken should return TOKEN_EXPIRED for expired token","status":"passed","title":"should return TOKEN_EXPIRED for expired token","duration":1.0046250000000327,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","validateToken"],"fullName":"TokenStore validateToken should return ACTION_NOT_PERMITTED for unauthorized action","status":"passed","title":"should return ACTION_NOT_PERMITTED for unauthorized action","duration":0.3747920000000704,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","getTokensByMiniapp"],"fullName":"TokenStore getTokensByMiniapp should return all tokens for a miniapp","status":"passed","title":"should return all tokens for a miniapp","duration":0.8923330000000078,"failureMessages":[],"meta":{}},{"ancestorTitles":["TokenStore","getTokensByMiniapp"],"fullName":"TokenStore getTokensByMiniapp should return empty array for miniapp with no tokens","status":"passed","title":"should return empty array for miniapp with no tokens","duration":0.23791700000015226,"failureMessages":[],"meta":{}}],"startTime":1768719959660,"endTime":1768719959676.238,"status":"passed","message":"","name":"/Users/kzf/Dev/bioforestChain/KeyApp/src/services/crypto-box/__tests__/token-store.test.ts"},{"assertionResults":[{"ancestorTitles":["CryptoBox Types","TOKEN_DURATION_MS"],"fullName":"CryptoBox Types TOKEN_DURATION_MS should have correct milliseconds for 5min","status":"passed","title":"should have correct milliseconds for 5min","duration":0.6167500000000246,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","TOKEN_DURATION_MS"],"fullName":"CryptoBox Types TOKEN_DURATION_MS should have correct milliseconds for 15min","status":"passed","title":"should have correct milliseconds for 15min","duration":0.10712499999999636,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","TOKEN_DURATION_MS"],"fullName":"CryptoBox Types TOKEN_DURATION_MS should have correct milliseconds for 1hour","status":"passed","title":"should have correct milliseconds for 1hour","duration":0.08849999999995362,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","TOKEN_DURATION_MS"],"fullName":"CryptoBox Types TOKEN_DURATION_MS should have correct milliseconds for 1day","status":"passed","title":"should have correct milliseconds for 1day","duration":0.07045900000002803,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","CryptoBoxErrorCodes"],"fullName":"CryptoBox Types CryptoBoxErrorCodes should have TOKEN_NOT_FOUND code","status":"passed","title":"should have TOKEN_NOT_FOUND code","duration":0.11162500000000364,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","CryptoBoxErrorCodes"],"fullName":"CryptoBox Types CryptoBoxErrorCodes should have MINIAPP_MISMATCH code","status":"passed","title":"should have MINIAPP_MISMATCH code","duration":0.057792000000063126,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","CryptoBoxErrorCodes"],"fullName":"CryptoBox Types CryptoBoxErrorCodes should have TOKEN_EXPIRED code","status":"passed","title":"should have TOKEN_EXPIRED code","duration":0.044125000000008185,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","CryptoBoxErrorCodes"],"fullName":"CryptoBox Types CryptoBoxErrorCodes should have ACTION_NOT_PERMITTED code","status":"passed","title":"should have ACTION_NOT_PERMITTED code","duration":0.04045799999994415,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","CryptoBoxErrorCodes"],"fullName":"CryptoBox Types CryptoBoxErrorCodes should have USER_REJECTED code","status":"passed","title":"should have USER_REJECTED code","duration":0.05224999999995816,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should accept valid CryptoAction values","status":"passed","title":"should accept valid CryptoAction values","duration":0.4608340000000908,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should accept valid TokenDuration values","status":"passed","title":"should accept valid TokenDuration values","duration":0.08908399999995709,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should create valid CryptoToken","status":"passed","title":"should create valid CryptoToken","duration":0.3617910000000393,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should create valid StoredToken with patternHash","status":"passed","title":"should create valid StoredToken with patternHash","duration":0.05104200000005221,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should create valid RequestCryptoTokenParams","status":"passed","title":"should create valid RequestCryptoTokenParams","duration":0.040541999999959444,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should create valid CryptoExecuteParams for asymmetricEncrypt","status":"passed","title":"should create valid CryptoExecuteParams for asymmetricEncrypt","duration":0.03962500000000091,"failureMessages":[],"meta":{}},{"ancestorTitles":["CryptoBox Types","Type guards"],"fullName":"CryptoBox Types Type guards should create valid CryptoExecuteParams for sign","status":"passed","title":"should create valid CryptoExecuteParams for sign","duration":0.042167000000063126,"failureMessages":[],"meta":{}}],"startTime":1768719959643,"endTime":1768719959646.051,"status":"passed","message":"","name":"/Users/kzf/Dev/bioforestChain/KeyApp/src/services/crypto-box/__tests__/types.test.ts"}]} \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 949431c96..5186a49d3 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -3,11 +3,7 @@ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2023", "useDefineForClassFields": true, - "lib": [ - "ES2023", - "DOM", - "DOM.Iterable" - ], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, /* Bundler mode */ @@ -29,49 +25,26 @@ /* Path aliases */ "baseUrl": ".", "paths": { - "@/*": [ - "./src/*" - ], - "#biometric-impl": [ - "./src/services/biometric/web.ts" - ], - "#clipboard-impl": [ - "./src/services/clipboard/web.ts" - ], - "#toast-impl": [ - "./src/services/toast/web.ts" - ], - "#haptics-impl": [ - "./src/services/haptics/web.ts" - ], - "#storage-impl": [ - "./src/services/storage/web.ts" - ], - "#camera-impl": [ - "./src/services/camera/web.ts" - ], - "#authorize-impl": [ - "./src/services/authorize/web.ts" - ], - "#currency-exchange-impl": [ - "./src/services/currency-exchange/web.ts" - ], - "#staking-impl": [ - "./src/services/staking/web.ts" - ], - "#transaction-impl": [ - "./src/services/transaction/web.ts" - ], + "@/*": ["./src/*"], + "#biometric-impl": ["./src/services/biometric/web.ts"], + "#clipboard-impl": ["./src/services/clipboard/web.ts"], + "#toast-impl": ["./src/services/toast/web.ts"], + "#haptics-impl": ["./src/services/haptics/web.ts"], + "#storage-impl": ["./src/services/storage/web.ts"], + "#camera-impl": ["./src/services/camera/web.ts"], + "#authorize-impl": ["./src/services/authorize/web.ts"], + "#currency-exchange-impl": ["./src/services/currency-exchange/web.ts"], + "#staking-impl": ["./src/services/staking/web.ts"], + "#transaction-impl": ["./src/services/transaction/web.ts"], }, }, - "include": [ - "src" - ], + "include": ["src"], "exclude": [ "src/**/*.test.ts", + "src/i18n/**", "src/**/*.test.tsx", "src/**/*.stories.ts", "src/**/*.stories.tsx", - "packages/**/*" - ] -} \ No newline at end of file + "packages/**/*", + ], +} diff --git a/vite.config.ts b/vite.config.ts index eb402ed6d..c1144d5a9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,7 @@ import { networkInterfaces } from 'node:os' import { resolve } from 'node:path' import { mockDevToolsPlugin } from './scripts/vite-plugin-mock-devtools' import { miniappsPlugin } from './scripts/vite-plugin-miniapps' +import { remoteMiniappsPlugin } from './scripts/vite-plugin-remote-miniapps' import { buildCheckPlugin } from './scripts/vite-plugin-build-check' function getPreferredLanIPv4(): string | undefined { @@ -90,6 +91,16 @@ export default defineConfig(({ mode }) => { react(), tailwindcss(), mockDevToolsPlugin(), + // 远程 miniapps (必须在 miniappsPlugin 之前,以便注册到全局状态) + remoteMiniappsPlugin({ + miniapps: [ + // RWA Hub - 从远程下载 + { + metadataUrl: 'https://iweb.xin/rwahub.bfmeta.com.miniapp/metadata.json', + dirName: 'rwa-hub', + }, + ], + }), miniappsPlugin(), buildCheckPlugin(), ],