diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9262fc7b..8af418f1 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -65,7 +65,9 @@ "Bash(for REF in \"http://localhost:8080/\" \"https://dev.fastbee.cn/\" \"https://www.tianditu.gov.cn/\")", "Read(//tmp/**)", "Bash(node -e ' *)", - "Bash(read c *)" + "Bash(read c *)", + "Bash(tar xzf *)", + "mcp__filesystem__create_directory" ] } } diff --git a/.claude/skills/yack-mini-dev.md b/.claude/skills/yack-mini-dev.md new file mode 100644 index 00000000..6790b3f4 --- /dev/null +++ b/.claude/skills/yack-mini-dev.md @@ -0,0 +1,359 @@ +# yack-mini 开发指南 + +微信小程序蓝牙网关项目,采集 BLE 设备数据透传上报至 yack 平台。 + +## 项目定位 + +- **名称**:yack-mini +- **技术栈**:原生微信小程序 +- **目标**:轻量化蓝牙网关,透传数据至 yack +- **与 yack-app 区别**:yack-app 是全功能移动端(UniApp),yack-mini 是轻量蓝牙网关(原生小程序) + +## 架构概览 + +``` +┌─────────────┐ BLE ┌──────────────────┐ wxs ┌─────────────────┐ +│ BLE 设备 │ ←──────→ │ yack-mini │ ←──────→ │ yack 平台 │ +│ (传感器) │ │ (微信小程序) │ MQTT │ (数据存储) │ +└─────────────┘ └──────────────────┘ └─────────────────┘ +``` + +## 项目结构 + +``` +miniprogram/ +├── app.js # 应用入口 +├── app.json # 全局配置 +├── app.wxss # 全局样式 +├── project.config.json # 项目配置(需修改 appid) +├── sitemap.json # 站点地图 +├── env.config.js # 环境配置(API/MQTT 地址) +│ +├── pages/ +│ ├── login/ # 登录页(首次使用时获取 userId) +│ │ ├── login.js +│ │ ├── login.wxml +│ │ ├── login.wxss +│ │ └── login.json +│ │ +│ ├── index/ # 首页(设备列表) +│ │ ├── index.js +│ │ ├── index.wxml +│ │ ├── index.wxss +│ │ └── index.json +│ │ +│ └── device/ +│ ├── scan/ # 扫描设备页 +│ │ ├── scan.js +│ │ ├── scan.wxml +│ │ ├── scan.wxss +│ │ └── scan.json +│ │ +│ └── detail/ # 设备详情/实时数据页 +│ ├── detail.js +│ ├── detail.wxml +│ ├── detail.wxss +│ └── detail.json +│ +├── services/ # 核心服务模块 +│ ├── ble.js # 蓝牙采集模块 +│ │ • initBluetooth() # 初始化蓝牙适配器 +│ │ • scanDevices(uuids, timeout) # 扫描设备 +│ │ • stopScan() # 停止扫描 +│ │ • connectDevice(id, serviceId) # 连接设备 +│ │ • onData(callback) # 订阅数据通知(非 subscribeData) +│ │ • onStateChange(cb) # 状态变化回调 +│ │ • writeCommand(hex) # 写入指令 +│ │ • disconnect() # 断开连接 +│ │ • closeBluetooth() # 关闭蓝牙适配器 +│ │ • getState() # 获取当前状态 +│ │ • isConnected() # 是否已连接 +│ │ +│ └── mqtt-client.js # MQTT 连接模块 +│ • connect(options) # 连接 Broker +│ • publish(topic, msg, opts) # 发布消息 +│ • subscribe(topic, cb, opts) # 订阅主题 +│ • unsubscribe(topic) # 取消订阅 +│ • onMessage(cb) # 消息回调 +│ • onStateChange(cb) # 状态变化回调 +│ • disconnect() # 断开连接 +│ • getState() # 获取当前状态 +│ • isConnected() # 是否已连接 +│ +├── utils/ +│ ├── hex.js # 十六进制工具 +│ │ • arrayBufferToHex(buf) # ArrayBuffer → Hex +│ │ • hexToArrayBuffer(hex) # Hex → ArrayBuffer +│ │ +│ ├── storage.js # 本地存储 +│ │ • getToken/setToken/removeToken # Token 管理 +│ │ • getUserInfo/setUserInfo/removeUserInfo # 用户信息 +│ │ • getDeviceList/saveDeviceList # 设备列表 +│ │ • addDevice(device) # 添加设备(非 saveDevice) +│ │ • removeDevice/updateDevice/getDevice +│ │ • cachePendingData/getPendingData/clearPendingData # 离线缓存 +│ │ • clearAll() # 清除所有数据 +│ │ +│ ├── request.js # HTTP 请求模块(仅登录使用) +│ │ • login(username, password, code, uuid) # 登录 +│ │ • getInfo() # 获取用户信息 +│ │ • captchaImage() # 获取验证码 +│ │ +│ └── auth.js # MQTT 凭证生成 +│ • generateClientId(serialNumber, productId, userId) # 生成 clientId +│ • getMqttCredentials(serialNumber, productId, productSecret) # 获取凭证 +│ • getMqttCredentialsFromDevice(device) # 从设备配置获取凭证 +│ • buildPropertyPostTopic(productId, serialNumber) # 构建上报 Topic +│ • buildFunctionGetTopic(productId, serialNumber) # 构建指令 Topic +│ • buildPropertyGetTopic(productId, serialNumber) # 构建读取 Topic +│ • checkLoginStatus() # 检查登录状态 +│ • getCurrentUserId() # 获取当前用户 ID +│ +├── components/ # 公共组件 +│ └── device-card/ # 设备卡片组件 +│ +├── libs/ # 第三方库 +│ └── mqtt.min.js # MQTT 客户端(预置,非 npm 安装) +│ +└── assets/ # 静态资源 + └── images/ +``` + +## 登录机制说明 + +### 设计原则 + +**登录仅用于首次获取 userId,后续无需重复登录。** + +- userId 用于生成 MQTT clientId,是设备上报数据的必要参数 +- 登录成功后,userId 永久保存在本地 +- 后续启动直接读取本地 userId,无需网络请求 +- Token 不参与后续业务逻辑(仅登录时使用) + +### 登录流程 + +``` +首次启动: + 检查本地 userId → 无 → 显示登录页 → 输入账号密码 → 获取 userId → 保存本地 → 进入设备列表 + +后续启动: + 检查本地 userId → 有 → 直接进入设备列表(无需登录) +``` + +### 退出登录 + +如需切换账号,在设置页或设备列表页提供"退出登录"按钮,调用: + +```javascript +const storage = require('../../utils/storage.js'); + +// 退出登录 +function logout() { + storage.clearAll(); // 清除本地所有数据 + wx.reLaunch({ url: '/pages/login/login' }); // 跳转登录页 +} +``` + +## 关键技术实现 + +### 1. 蓝牙 BLE 采集(services/ble.js) + +```javascript +// 核心流程 +wx.openBluetoothAdapter() // 初始化 + → wx.startBluetoothDevicesDiscovery() // 扫描 + → wx.createBLEConnection() // 连接 + → wx.getBLEDeviceServices() // 获取服务 + → wx.getBLEDeviceCharacteristics() // 获取特征值 + → wx.notifyBLECharacteristicValueChange() // 启用通知 + → wx.onBLECharacteristicValueChange() // 监听数据 +``` + +**使用示例:** + +```javascript +const { bleService } = require('../../services/ble.js'); + +// 初始化并连接 +await bleService.initBluetooth(); +bleService.onData((data) => { + console.log('收到数据:', data.hex); // hex 字符串 +}); + +const devices = await bleService.scanDevices([], 5000); +await bleService.connectDevice(devices[0].deviceId); +``` + +### 2. MQTT 连接(services/mqtt-client.js) + +**库文件**:使用预置的 `libs/mqtt.min.js`(非 npm 安装) + +**连接配置:** + +```javascript +{ + clientId: 'S&{设备编号}&{产品ID}&{用户ID}', // 简单认证 + username: '{设备编号}', + password: '{产品密钥}', + clean: true, + keepalive: 30 +} +``` + +**协议**:`wxs://domain.com:8084/mqtt`(微信小程序专用) + +**使用示例:** + +```javascript +const { mqttClient } = require('../../services/mqtt-client.js'); +const auth = require('../../utils/auth.js'); + +// 获取凭证 +const credentials = auth.getMqttCredentialsFromDevice(device); + +// 连接 +await mqttClient.connect({ + clientId: credentials.clientId, + username: credentials.username, + password: credentials.password, +}); + +// 发布数据 +const topic = auth.buildPropertyPostTopic(device.productId, device.serialNumber); +await mqttClient.publish(topic, hexData, { qos: 1 }); + +// 订阅指令 +const cmdTopic = auth.buildFunctionGetTopic(device.productId, device.serialNumber); +await mqttClient.subscribe(cmdTopic, (topic, message) => { + console.log('收到指令:', message); +}); +``` + +### 3. 数据透传格式 + +| 场景 | 上报格式 | Topic | +|:-----|:--------|:------| +| Modbus-RTU | Hex 字符串(原始帧) | `/{productId}/{sn}/property/post` | +| 其他协议 | Hex 字符串(原始数据) | 同上(平台脚本解析) | + +**示例:** + +```javascript +// Modbus 帧上报 +mqttClient.publish('/1/BLE001/property/post', '010304009A005B8B7C'); + +// 私有协议数据上报 +mqttClient.publish('/1/BLE002/property/post', '009A005B'); +``` + +## yack 平台配置 + +### 产品配置 + +1. **创建产品** + - 产品名称:蓝牙传感器 + - 协议:`MODBUS-RTU` 或 `JSON`(取决于设备协议) + +2. **定义物模型** + - 属性:temperature、humidity 等 + - 标识符:与 Modbus 配置/脚本解析对应 + +3. **Modbus 配置**(`iot_modbus_config`,仅 Modbus 设备) + - 从站地址、功能码、寄存器地址、数据类型 + +4. **规则脚本**(`iot_script`,仅非 Modbus 设备) + - 脚本用途:数据流处理(1) + - 触发事件:设备上报(1) + - 脚本内容:解析 hex 数据,输出物模型 JSON + +### 设备配置 + +- 设备编号:小程序 clientId 中的 `{设备编号}` +- 产品密钥:作为 MQTT password + +## 开发规范 + +### 代码规范 + +1. **命名规范** + - 文件名:小写 + 连字符(`device-card.js`) + - 变量名:小驼峰(`deviceId`) + - 常量:大写 + 下划线(`SERVICE_UUID`) + - 私有方法:下划线前缀(`_handleData()`) + +2. **注释规范** + ```javascript + /** + * 扫描蓝牙设备 + * @param {Array} serviceUUIDs - 服务 UUID 列表 + * @returns {Promise} 设备列表 + */ + async scanDevices(serviceUUIDs) { ... } + ``` + +3. **错误处理** + - 所有异步操作使用 try-catch + - 用户友好错误提示(`wx.showToast`) + +### 包体积控制 + +**目标**:主包 < 1.5MB + +**策略**: +1. 不使用 UI 框架(原生组件) +2. mqtt.js 已压缩并预置在 libs 目录 +3. 图片使用 CDN 或压缩 +4. 分包加载(如有需要) + +## 调试与测试 + +### 微信开发者工具 + +1. **真机调试**:蓝牙功能必须在真机上测试 +2. **MQTT 调试**:使用 `wxs://` 协议,确保域名配置正确 + +### 平台验证 + +1. yack 控制台查看设备数据 +2. 检查 `iot_device_log` 表 +3. 查看规则脚本日志 + +## 部署流程 + +1. **微信公众平台配置** + - 服务器域名:添加 `socket 合法域名`(wxs://...) + - 业务域名:如需 H5 页面 + +2. **项目配置** + - 修改 `project.config.json` 中的 `appid` + - 修改 `env.config.js` 中的 API 和 MQTT 地址 + - 将 `ENV` 改为 `'production'` + +3. **代码上传** + - 微信开发者工具 → 上传代码 + - 设置版本号、备注 + +4. **提交审核** + - 填写功能说明 + - 蓝牙权限说明 + +## 扩展路径 + +1. **新增 Modbus 设备** + - 平台配置 Modbus 参数 + - 小程序扫描新设备(可能需更新 Service UUID) + +2. **新增私有协议设备** + - 平台编写规则脚本 + - 小程序适配 BLE 特征值 + +3. **设备控制(下发指令)** + - 订阅 `/function/get` 或 `/property/get` + - 通过 `bleService.writeCommand()` 发送 + +## 相关文档 + +- [微信小程序蓝牙 API](https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html) +- [mqtt.js 文档](https://github.com/mqttjs/MQTT.js) +- [yack 协议解析](CLAUDE.md#架构说明) +- [Modbus 配置表](sql/yack-init.sql 中 `iot_modbus_config`) \ No newline at end of file diff --git a/memory/MEMORY.md b/memory/MEMORY.md new file mode 100644 index 00000000..73f8e157 --- /dev/null +++ b/memory/MEMORY.md @@ -0,0 +1,9 @@ +# Memory Index + +- [Platform integration direction](project_platform_integration_direction.md) — Shared iPaaS/flow orchestration with tenant-specific mappings; DCP one point per device. +- [yack-web Node version](project_yack_web_node_version.md) — Use Node 16.20.2 via nvm before yack-web npm commands. +- [Vertical calculated model](project_vertical_calc_model.md) — isCalculated=2 accumulates from cached model value; no new state table. +- [Permission policy](project_permission_policy.md) — Cross-page read/query APIs use hasAnyPermi with consuming page permissions. +- [Homepage terminology](feedback_homepage_terminology.md) — "首页"指 bigScreen/home.vue;"后台管理首页"指 index.vue。 +- [Documentation output preference](feedback_documentation_output.md) — 只在用户明确要求时输出md文档。 +- [yack-mini tech stack](yack-mini_tech_stack.md) — 原生微信小程序蓝牙网关,透传 BLE 数据至 FastBee。 \ No newline at end of file diff --git a/memory/yack-mini_tech_stack.md b/memory/yack-mini_tech_stack.md new file mode 100644 index 00000000..c43f0bd6 --- /dev/null +++ b/memory/yack-mini_tech_stack.md @@ -0,0 +1,27 @@ +--- +name: yack-mini-tech-stack +description: yack-mini 使用原生微信小程序开发(非 UniApp),轻量化蓝牙网关 +metadata: + type: project +--- + +**项目名称**:yack-mini + +**技术栈**:原生微信小程序(WXML/WXSS/JS) + +**定位**:轻量化蓝牙网关,采集 BLE 设备数据透传至 FastBee + +**与 yack-app 区别**: +- yack-app:UniApp 全功能移动端(包大) +- yack-mini:原生小程序蓝牙网关(包小) + +**核心功能**: +1. BLE 设备扫描、连接、数据采集 +2. MQTT over wxs:// 上报数据 +3. 透传模式(Modbus 原始帧 / 其他协议 hex 数据) + +**开发 skill**:[[yack-mini-dev]] + +**Why**:用户反馈 yack-app 包体积过大无法发布小程序,故新建轻量化项目 + +**How to apply**:开发 yack-mini 时调用 `/yack-mini-dev` skill 获取项目架构和规范 \ No newline at end of file diff --git a/yack-mini/.gitignore b/yack-mini/.gitignore new file mode 100644 index 00000000..4883dfd2 --- /dev/null +++ b/yack-mini/.gitignore @@ -0,0 +1,12 @@ +# 微信开发者工具 +.DS_Store +*.mp3 +*.mp4 + +# 依赖目录(如果有使用 npm) +node_modules/ +miniprogram_npm/ + +# 临时文件 +*.log +*.tmp \ No newline at end of file diff --git a/yack-mini/README.md b/yack-mini/README.md new file mode 100644 index 00000000..4bcdc940 --- /dev/null +++ b/yack-mini/README.md @@ -0,0 +1,205 @@ +# yack-mini + +yack 蓝牙网关微信小程序,采集 BLE 设备数据透传上报至 yack 平台。 + +## 项目定位 + +- **技术栈**:原生微信小程序 +- **目标**:轻量化蓝牙网关,透传数据至 yack +- **与 yack-app 区别**:yack-app 是全功能移动端(UniApp),yack-mini 是轻量蓝牙网关(原生小程序) + +## 架构概览 + +``` +┌─────────────┐ BLE ┌──────────────────┐ wxs ┌─────────────────┐ +│ BLE 设备 │ ←──────→ │ yack-mini │ ←──────→ │ yack 平台 │ +│ (传感器) │ │ (微信小程序) │ MQTT │ (数据存储) │ +└─────────────┘ └──────────────────┘ └─────────────────┘ +``` + +## 目录结构 + +``` +yack-mini/ +├── project.config.json # 项目配置 +└── miniprogram/ # 小程序源码 + ├── app.js # 应用入口 + ├── app.json # 全局配置 + ├── app.wxss # 全局样式 + ├── env.config.js # 环境配置(API/MQTT 地址) + ├── sitemap.json # 站点地图 + │ + ├── pages/ # 页面 + │ ├── login/ # 登录页 + │ ├── index/ # 首页(设备列表) + │ └── device/ + │ ├── scan/ # 扫描设备页 + │ └── detail/ # 设备详情/实时数据页 + │ + ├── services/ # 核心服务模块 + │ ├── ble.js # 蓝牙采集模块 + │ └── mqtt-client.js # MQTT 连接模块 + │ + ├── utils/ # 工具函数 + │ ├── hex.js # 十六进制转换 + │ ├── storage.js # 本地存储 + │ ├── auth.js # MQTT 凭证获取 + │ └── request.js # HTTP 请求 + │ + ├── components/ # 公共组件 + │ └── device-card/ # 设备卡片组件 + │ + ├── libs/ # 第三方库 + │ └── mqtt.min.js # mqtt.js (4.3.7, 支持微信小程序) + │ + └── assets/ # 静态资源 + └── images/ # 图片 +``` + +## 快速开始 + +### 1. 导入项目 + +1. 下载并安装 [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) +2. 打开微信开发者工具,选择「导入项目」 +3. 项目目录选择 `yack-mini` +4. AppID 使用测试号或填写正式 AppID + +### 2. 配置环境 + +编辑 `miniprogram/env.config.js`,配置 yack 平台地址: + +```javascript +// 开发环境 +const DEV_CONFIG = { + baseUrl: 'http://localhost:8080', + mqttServer: 'wxs://localhost:8084/mqtt', +}; + +// 生产环境 +const PROD_CONFIG = { + baseUrl: 'https://your-domain.com/prod-api', + mqttServer: 'wxs://your-domain.com/mqtt', +}; +``` + +### 3. 替换图片资源 + +`miniprogram/assets/images/` 目录下的图片为占位图,请替换为实际图片: + +- `logo.png` - 应用 Logo +- `device.png` / `device-active.png` - TabBar 图标 +- `device-empty.png` - 空状态图 +- `ble-scan.png` - 扫描页图标 +- `default-avatar.png` - 默认头像 + +### 4. 真机调试 + +蓝牙功能必须在真机上测试: +1. 点击「真机调试」 +2. 使用手机扫码预览 +3. 确保手机蓝牙已开启 + +## 核心功能 + +### 1. 登录认证 + +- yack 账号密码登录 +- 获取 Token 和用户信息(含 userId) +- 登录态持久化存储 + +### 2. BLE 设备扫描 + +- 初始化蓝牙适配器 +- 扫描周边 BLE 设备 +- 支持按 Service UUID 过滤 + +### 3. 设备配置 + +配置设备参数: +- 设备名称 +- 设备编号(SN) +- 产品 ID +- 产品密钥(用于 MQTT 认证) +- BLE 服务 UUID(可选) + +### 4. 实时数据采集 + +- BLE 连接与数据监听 +- 十六进制数据展示 +- 自动/手动上报到 yack + +### 5. MQTT 数据上报 + +- 连接 yack MQTT Broker(`wxs://` 协议) +- 发布属性数据到 `/{productId}/{sn}/property/post` +- 支持订阅下发指令 + +## MQTT 认证说明 + +设备连接 yack MQTT Broker 的凭证格式: + +| 参数 | 格式 | 示例 | +|:---|:---|:---| +| clientId | `S&{设备编号}&{产品ID}&{用户ID}` | `S&BLE001&1&100` | +| username | `{设备编号}` | `BLE001` | +| password | `{产品密钥}` | `abc123` | + +## 数据上报格式 + +BLE 设备数据以十六进制字符串上报: + +```javascript +// Modbus 帧示例 +mqttClient.publish('/1/BLE001/property/post', '010304009A005B8B7C'); + +// 私有协议示例 +mqttClient.publish('/1/BLE002/property/post', '009A005B'); +``` + +yack 平台通过规则脚本或 Modbus 配置解析数据。 + +## 微信小程序配置 + +### 服务器域名 + +在微信公众平台配置: + +- **socket 合法域名**:`wxs://your-domain.com` +- **request 合法域名**:`https://your-domain.com` + +### 权限说明 + +- `scope.bluetooth`:蓝牙权限(部分设备需要用户授权) +- `scope.userLocation`:部分 Android 设备扫描 BLE 需要位置权限 + +## 开发调试 + +### BLE 调试 + +1. 使用真机调试 +2. 查看 Console 日志,前缀 `[BLE]` +3. 使用 nRF Connect 等工具验证 BLE 设备 + +### MQTT 调试 + +1. 查看 Console 日志,前缀 `[MQTT]` +2. 使用 MQTT 客户端工具验证连接 +3. 确保 `wxs://` 域名已在微信公众平台配置 + +## 注意事项 + +1. **蓝牙权限**:iOS 无需额外授权,Android 可能需要位置权限 +2. **MQTT 协议**:微信小程序必须使用 `wxs://` 协议(端口 8084) +3. **数据格式**:BLE 数据需转换为十六进制字符串上报 +4. **包体积**:主包控制在 1.5MB 以内 + +## 相关文档 + +- [微信小程序蓝牙 API](https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html) +- [mqtt.js 文档](https://github.com/mqttjs/MQTT.js) +- [yack 文档](../CLAUDE.md) + +## License + +MIT \ No newline at end of file diff --git a/yack-mini/miniprogram/app.js b/yack-mini/miniprogram/app.js new file mode 100644 index 00000000..3d225e69 --- /dev/null +++ b/yack-mini/miniprogram/app.js @@ -0,0 +1,88 @@ +/** + * 应用入口 + */ +const envConfig = require('./env.config.js'); +const storage = require('./utils/storage.js'); + +App({ + globalData: { + // 用户信息 + userInfo: null, + userId: null, + token: null, + // 环境配置 + envConfig: envConfig, + // 当前连接的设备 + currentDevice: null, + // MQTT 客户端 + mqttClient: null, + }, + + onLaunch() { + console.log('[App] onLaunch'); + // 检查登录态 + this.checkLoginStatus(); + }, + + /** + * 检查登录状态 + * 登录态只依赖 userId(登录一次即可,业务流程不使用 token) + */ + checkLoginStatus() { + const userInfo = storage.getUserInfo(); + if (userInfo && userInfo.userId) { + this.globalData.userInfo = userInfo; + this.globalData.userId = userInfo.userId; + // token 可选保存(仅登录流程使用,业务不依赖) + this.globalData.token = storage.getToken() || null; + console.log('[App] 已登录, userId:', userInfo.userId); + } else { + console.log('[App] 未登录'); + } + }, + + /** + * 设置登录信息 + * @param {Object} data - 登录返回数据 { token, user } + */ + setLoginInfo(data) { + const userInfo = { + userId: data.userId, + userName: data.userName, + phonenumber: data.phonenumber, + avatar: data.avatar, + }; + this.globalData.token = data.token; + this.globalData.userInfo = userInfo; + this.globalData.userId = data.userId; + + // 持久化存储 + storage.setToken(data.token); + storage.setUserInfo(userInfo); + + console.log('[App] 登录成功, userId:', data.userId); + }, + + /** + * 清除登录信息 + */ + clearLoginInfo() { + this.globalData.token = null; + this.globalData.userInfo = null; + this.globalData.userId = null; + + storage.removeToken(); + storage.removeUserInfo(); + + console.log('[App] 已退出登录'); + }, + + /** + * 检查是否已登录 + * 只检查 userId,token 过期不影响登录态(登录一次即可) + * @returns {boolean} + */ + isLoggedIn() { + return !!this.globalData.userId; + }, +}); \ No newline at end of file diff --git a/yack-mini/miniprogram/app.json b/yack-mini/miniprogram/app.json new file mode 100644 index 00000000..b01dbfe6 --- /dev/null +++ b/yack-mini/miniprogram/app.json @@ -0,0 +1,32 @@ +{ + "pages": [ + "pages/login/login", + "pages/index/index", + "pages/device/scan/scan", + "pages/device/detail/detail" + ], + "window": { + "navigationBarTitleText": "岩安测控网关", + "navigationBarBackgroundColor": "#1890ff", + "navigationBarTextStyle": "white", + "backgroundColor": "#f5f5f5", + "backgroundTextStyle": "dark" + }, + "tabBar": { + "color": "#999999", + "selectedColor": "#1890ff", + "backgroundColor": "#ffffff", + "borderStyle": "white", + "list": [ + { + "pagePath": "pages/index/index", + "text": "设备", + "iconPath": "assets/images/device.png", + "selectedIconPath": "assets/images/device-active.png" + } + ] + }, + "style": "v2", + "sitemapLocation": "sitemap.json", + "lazyCodeLoading": "requiredComponents" +} \ No newline at end of file diff --git a/yack-mini/miniprogram/app.wxss b/yack-mini/miniprogram/app.wxss new file mode 100644 index 00000000..73caca9c --- /dev/null +++ b/yack-mini/miniprogram/app.wxss @@ -0,0 +1,147 @@ +/** + * 全局样式 + */ + +/* 页面基础样式 */ +page { + background-color: #f5f5f5; + font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif; + font-size: 28rpx; + color: #333; + line-height: 1.6; +} + +/* 通用容器 */ +.container { + padding: 20rpx; +} + +/* 卡片样式 */ +.card { + background-color: #fff; + border-radius: 16rpx; + padding: 24rpx; + margin-bottom: 20rpx; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); +} + +/* 按钮样式 */ +.btn-primary { + background-color: #1890ff; + color: #fff; + border-radius: 8rpx; + font-size: 32rpx; + padding: 20rpx 40rpx; + text-align: center; +} + +.btn-primary:active { + background-color: #096dd9; +} + +.btn-default { + background-color: #fff; + color: #333; + border: 1rpx solid #d9d9d9; + border-radius: 8rpx; + font-size: 32rpx; + padding: 20rpx 40rpx; + text-align: center; +} + +.btn-default:active { + background-color: #f5f5f5; +} + +.btn-danger { + background-color: #ff4d4f; + color: #fff; + border-radius: 8rpx; + font-size: 32rpx; + padding: 20rpx 40rpx; + text-align: center; +} + +/* 输入框样式 */ +.input { + background-color: #f5f5f5; + border-radius: 8rpx; + padding: 20rpx 24rpx; + font-size: 28rpx; +} + +/* 列表项 */ +.list-item { + display: flex; + flex-direction: row; + align-items: center; + padding: 24rpx 0; + border-bottom: 1rpx solid #f0f0f0; +} + +.list-item:last-child { + border-bottom: none; +} + +/* 标题 */ +.title { + font-size: 32rpx; + font-weight: 600; + color: #333; +} + +/* 副标题 */ +.subtitle { + font-size: 26rpx; + color: #999; + margin-top: 8rpx; +} + +/* 状态标签 */ +.status-tag { + display: inline-block; + padding: 4rpx 16rpx; + border-radius: 4rpx; + font-size: 22rpx; +} + +.status-tag.online { + background-color: #f6ffed; + color: #52c41a; + border: 1rpx solid #b7eb8f; +} + +.status-tag.offline { + background-color: #fff7e6; + color: #fa8c16; + border: 1rpx solid #ffd591; +} + +/* 空状态 */ +.empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 100rpx 40rpx; + color: #999; +} + +.empty-icon { + width: 200rpx; + height: 200rpx; + margin-bottom: 32rpx; +} + +.empty-text { + font-size: 28rpx; +} + +/* 加载中 */ +.loading { + display: flex; + justify-content: center; + align-items: center; + padding: 40rpx; + color: #999; +} \ No newline at end of file diff --git a/yack-mini/miniprogram/assets/images/ble-scan.png b/yack-mini/miniprogram/assets/images/ble-scan.png new file mode 100644 index 00000000..08cd6f2b Binary files /dev/null and b/yack-mini/miniprogram/assets/images/ble-scan.png differ diff --git a/yack-mini/miniprogram/assets/images/default-avatar.png b/yack-mini/miniprogram/assets/images/default-avatar.png new file mode 100644 index 00000000..08cd6f2b Binary files /dev/null and b/yack-mini/miniprogram/assets/images/default-avatar.png differ diff --git a/yack-mini/miniprogram/assets/images/device-active.png b/yack-mini/miniprogram/assets/images/device-active.png new file mode 100644 index 00000000..08cd6f2b Binary files /dev/null and b/yack-mini/miniprogram/assets/images/device-active.png differ diff --git a/yack-mini/miniprogram/assets/images/device-empty.png b/yack-mini/miniprogram/assets/images/device-empty.png new file mode 100644 index 00000000..08cd6f2b Binary files /dev/null and b/yack-mini/miniprogram/assets/images/device-empty.png differ diff --git a/yack-mini/miniprogram/assets/images/device.png b/yack-mini/miniprogram/assets/images/device.png new file mode 100644 index 00000000..08cd6f2b Binary files /dev/null and b/yack-mini/miniprogram/assets/images/device.png differ diff --git a/yack-mini/miniprogram/assets/images/logo.png b/yack-mini/miniprogram/assets/images/logo.png new file mode 100644 index 00000000..08cd6f2b Binary files /dev/null and b/yack-mini/miniprogram/assets/images/logo.png differ diff --git a/yack-mini/miniprogram/components/device-card/device-card.js b/yack-mini/miniprogram/components/device-card/device-card.js new file mode 100644 index 00000000..58526c19 --- /dev/null +++ b/yack-mini/miniprogram/components/device-card/device-card.js @@ -0,0 +1,45 @@ +/** + * 设备卡片组件 + */ +Component({ + properties: { + // 设备对象 { serialNumber, name, productId, productName, enabled, connected } + device: { + type: Object, + value: {}, + }, + }, + + methods: { + /** + * 点击卡片 + */ + handleTap() { + this.triggerEvent('tap', { device: this.data.device }); + }, + + /** + * 阻止事件冒泡 + */ + preventBubble() {}, + + /** + * 启停切换 + */ + handleSwitch(e) { + this.triggerEvent('switch', { + serialNumber: this.data.device.serialNumber, + enabled: e.detail.value, + }); + }, + + /** + * 删除设备 + */ + handleDelete() { + this.triggerEvent('delete', { + serialNumber: this.data.device.serialNumber, + }); + }, + }, +}); \ No newline at end of file diff --git a/yack-mini/miniprogram/components/device-card/device-card.json b/yack-mini/miniprogram/components/device-card/device-card.json new file mode 100644 index 00000000..e8cfaaf8 --- /dev/null +++ b/yack-mini/miniprogram/components/device-card/device-card.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/yack-mini/miniprogram/components/device-card/device-card.wxml b/yack-mini/miniprogram/components/device-card/device-card.wxml new file mode 100644 index 00000000..4c4e6419 --- /dev/null +++ b/yack-mini/miniprogram/components/device-card/device-card.wxml @@ -0,0 +1,37 @@ + + + + + + {{device.name || device.deviceName || '未命名设备'}} + + {{device.connected ? '已连接' : '未连接'}} + + + + + + 编号: + {{device.serialNumber}} + + + 产品: + {{device.productName || device.productId}} + + + + + + + + + + 删除 + + \ No newline at end of file diff --git a/yack-mini/miniprogram/components/device-card/device-card.wxss b/yack-mini/miniprogram/components/device-card/device-card.wxss new file mode 100644 index 00000000..61a3d567 --- /dev/null +++ b/yack-mini/miniprogram/components/device-card/device-card.wxss @@ -0,0 +1,98 @@ +/* 设备卡片组件样式 */ +.device-card { + background-color: #fff; + border-radius: 16rpx; + padding: 24rpx; + margin-bottom: 20rpx; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: row; + align-items: center; +} + +.device-main { + flex: 1; + display: flex; + flex-direction: column; +} + +.device-header { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + margin-bottom: 16rpx; +} + +.device-name { + font-size: 32rpx; + font-weight: 600; + color: #333; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.status-tag { + display: inline-block; + padding: 4rpx 16rpx; + border-radius: 4rpx; + font-size: 22rpx; + margin-left: 16rpx; + flex-shrink: 0; +} + +.status-tag.online { + background-color: #f6ffed; + color: #52c41a; + border: 1rpx solid #b7eb8f; +} + +.status-tag.offline { + background-color: #fff7e6; + color: #fa8c16; + border: 1rpx solid #ffd591; +} + +.device-info { + display: flex; + flex-direction: column; +} + +.info-row { + display: flex; + flex-direction: row; + margin-bottom: 8rpx; +} + +.info-label { + font-size: 24rpx; + color: #999; + width: 100rpx; +} + +.info-value { + font-size: 24rpx; + color: #666; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.device-actions { + display: flex; + flex-direction: column; + align-items: flex-end; + margin-left: 20rpx; +} + +.device-switch { + transform: scale(0.8); + margin-bottom: 12rpx; +} + +.delete-btn { + font-size: 24rpx; + color: #ff4d4f; + padding: 4rpx 12rpx; +} \ No newline at end of file diff --git a/yack-mini/miniprogram/env.config.js b/yack-mini/miniprogram/env.config.js new file mode 100644 index 00000000..5585f230 --- /dev/null +++ b/yack-mini/miniprogram/env.config.js @@ -0,0 +1,21 @@ +/** + * 环境配置 + * yack 平台 API 和 MQTT 服务地址 + */ + +// 开发环境 +const DEV_CONFIG = { + baseUrl: 'http://localhost:8080', + mqttServer: 'wxs://localhost:8084/mqtt', +}; + +// 生产环境 +const PROD_CONFIG = { + baseUrl: 'https://ycdat.cn/prod-api', + mqttServer: 'wxs://ycdat.cn/mqtt', +}; + +// 当前环境:development | production +const ENV = 'development'; + +module.exports = ENV === 'development' ? DEV_CONFIG : PROD_CONFIG; \ No newline at end of file diff --git a/yack-mini/miniprogram/libs/mqtt.min.js b/yack-mini/miniprogram/libs/mqtt.min.js new file mode 100644 index 00000000..953dfe75 --- /dev/null +++ b/yack-mini/miniprogram/libs/mqtt.min.js @@ -0,0 +1 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).mqtt=e()}}(function(){return function(){return function e(t,r,n){function i(s,a){if(!r[s]){if(!t[s]){var l="function"==typeof require&&require;if(!a&&l)return l(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=r[s]={exports:{}};t[s][0].call(c.exports,function(e){return i(t[s][1][e]||e)},c,c.exports,e,t,r,n)}return r[s].exports}for(var o="function"==typeof require&&require,s=0;s0&&(t.topicAliasMaximum>65535?y("MqttClient :: options.topicAliasMaximum is out of range"):this.topicAliasRecv=new s(t.topicAliasMaximum)),this.on("connect",function(){const e=this.queue;y("connect :: sending queued packets"),function t(){const r=e.shift();y("deliver :: entry %o",r);let i=null;if(!r)return void n._resubscribe();i=r.packet,y("deliver :: call _sendPacket for %o",i);let o=!0;i.messageId&&0!==i.messageId&&(n.messageIdProvider.register(i.messageId)||(o=!1)),o?n._sendPacket(i,function(e){r.cb&&r.cb(e),t()}):(y("messageId: %d has already used. The message is skipped and removed.",i.messageId),t())}()}),this.on("close",function(){y("close :: connected set to `false`"),this.connected=!1,y("close :: clearing connackTimer"),clearTimeout(this.connackTimer),y("close :: clearing ping timer"),null!==n.pingTimer&&(n.pingTimer.clear(),n.pingTimer=null),this.topicAliasRecv&&this.topicAliasRecv.clear(),y("close :: calling _setupReconnect"),this._setupReconnect()}),i.call(this),y("MqttClient :: setting up stream"),this._setupStream()}h(T,i),T.prototype._setupStream=function(){const e=this,t=new c,r=l.parser(this.options);let n=null;const i=[];function o(){if(i.length)b(s);else{const e=n;n=null,e()}}function s(){y("work :: getting next packet in queue");const t=i.shift();if(t)y("work :: packet pulled from queue"),e._handlePacket(t,o);else{y("work :: no packets in queue");const e=n;n=null,y("work :: done flag is %s",!!e),e&&e()}}y("_setupStream :: calling method to clear reconnect"),this._clearReconnect(),y("_setupStream :: using streamBuilder provided to client to create stream"),this.stream=this.streamBuilder(this),r.on("packet",function(e){y("parser :: on packet push to packets array."),i.push(e)}),t._write=function(e,t,i){n=i,y("writable stream :: parsing buffer"),r.parse(e),s()},y("_setupStream :: pipe stream to writable stream"),this.stream.pipe(t),this.stream.on("error",function(t){y("streamErrorHandler :: error",t.message),w.includes(t.code)?(y("streamErrorHandler :: emitting error"),e.emit("error",t)):C(t)}),this.stream.on("close",function(){var t;y("(%s)stream :: on close",e.options.clientId),(t=e.outgoing)&&(y("flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function"),Object.keys(t).forEach(function(e){t[e].volatile&&"function"==typeof t[e].cb&&(t[e].cb(new Error("Connection closed")),delete t[e])})),y("stream: emit close to MqttClient"),e.emit("close")}),y("_setupStream: sending packet `connect`");const a=Object.create(this.options);if(a.cmd="connect",this.topicAliasRecv&&(a.properties||(a.properties={}),this.topicAliasRecv&&(a.properties.topicAliasMaximum=this.topicAliasRecv.max)),S(this,a),r.on("error",this.emit.bind(this,"error")),this.options.properties){if(!this.options.properties.authenticationMethod&&this.options.properties.authenticationData)return e.end(()=>this.emit("error",new Error("Packet has no Authentication Method"))),this;if(this.options.properties.authenticationMethod&&this.options.authPacket&&"object"==typeof this.options.authPacket){S(this,g({cmd:"auth",reasonCode:0},this.options.authPacket))}}this.stream.setMaxListeners(1e3),clearTimeout(this.connackTimer),this.connackTimer=setTimeout(function(){y("!!connectTimeout hit!! Calling _cleanUp with force `true`"),e._cleanUp(!0)},this.options.connectTimeout)},T.prototype._handlePacket=function(e,t){const r=this.options;if(5===r.protocolVersion&&r.properties&&r.properties.maximumPacketSize&&r.properties.maximumPacketSize0||!s())&&this._storeProcessingQueue.push({invoke:s,cbStorePut:r.cbStorePut,callback:n}),this},T.prototype.subscribe=function(){const e=this,t=new Array(arguments.length);for(let e=0;e0){const n={qos:r.qos};5===a&&(n.nl=r.nl||!1,n.rap=r.rap||!1,n.rh=r.rh||0,n.properties=r.properties),e._resubscribeTopics[r.topic]=n,t.push(r.topic)}}),e.messageIdToTopic[n.messageId]=t}return e.outgoing[n.messageId]={volatile:!0,cb:function(e,t){if(!e){const e=t.granted;for(let t=0;t0||!c())&&this._storeProcessingQueue.push({invoke:c,callback:o}),this},T.prototype.unsubscribe=function(){const e=this,t=new Array(arguments.length);for(let e=0;e0||!s())&&this._storeProcessingQueue.push({invoke:s,callback:n}),this},T.prototype.end=function(e,t,r){const n=this;function i(){y("end :: (%s) :: finish :: calling _cleanUp with force %s",n.options.clientId,e),n._cleanUp(e,()=>{y("end :: finish :: calling process.nextTick on closeStores"),b(function(){y("end :: closeStores: closing incoming and outgoing stores"),n.disconnected=!0,n.incomingStore.close(function(e){n.outgoingStore.close(function(t){if(y("end :: closeStores: emitting end"),n.emit("end"),r){const n=e||t;y("end :: closeStores: invoking callback with args"),r(n)}})}),n._deferredReconnect&&n._deferredReconnect()}.bind(n))},t)}return y("end :: (%s)",this.options.clientId),null!=e&&"boolean"==typeof e||(r=t||C,t=e,e=!1,"object"!=typeof t&&(r=t,t=null,"function"!=typeof r&&(r=C))),"object"!=typeof t&&(r=t,t=null),y("end :: cb? %s",!!r),r=r||C,this.disconnecting?(r(),this):(this._clearReconnect(),this.disconnecting=!0,!e&&Object.keys(this.outgoing).length>0?(y("end :: (%s) :: calling finish in 10ms once outgoing is empty",n.options.clientId),this.once("outgoingEmpty",setTimeout.bind(null,i,10))):(y("end :: (%s) :: immediately calling finish",n.options.clientId),i()),this)},T.prototype.removeOutgoingMessage=function(e){const t=this.outgoing[e]?this.outgoing[e].cb:null;return delete this.outgoing[e],this.outgoingStore.del({messageId:e},function(){t(new Error("Message removed"))}),this},T.prototype.reconnect=function(e){y("client reconnect");const t=this,r=function(){e?(t.options.incomingStore=e.incomingStore,t.options.outgoingStore=e.outgoingStore):(t.options.incomingStore=null,t.options.outgoingStore=null),t.incomingStore=t.options.incomingStore||new o,t.outgoingStore=t.options.outgoingStore||new o,t.disconnecting=!1,t.disconnected=!1,t._deferredReconnect=null,t._reconnect()};return this.disconnecting&&!this.disconnected?this._deferredReconnect=r:r(),this},T.prototype._reconnect=function(){y("_reconnect: emitting reconnect to client"),this.emit("reconnect"),this.connected?(this.end(()=>{this._setupStream()}),y("client already connected. disconnecting first.")):(y("_reconnect: calling _setupStream"),this._setupStream())},T.prototype._setupReconnect=function(){const e=this;!e.disconnecting&&!e.reconnectTimer&&e.options.reconnectPeriod>0?(this.reconnecting||(y("_setupReconnect :: emit `offline` state"),this.emit("offline"),y("_setupReconnect :: set `reconnecting` to `true`"),this.reconnecting=!0),y("_setupReconnect :: setting reconnectTimer for %d ms",e.options.reconnectPeriod),e.reconnectTimer=setInterval(function(){y("reconnectTimer :: reconnect triggered!"),e._reconnect()},e.options.reconnectPeriod)):y("_setupReconnect :: doing nothing...")},T.prototype._clearReconnect=function(){y("_clearReconnect : clearing reconnect timer"),this.reconnectTimer&&(clearInterval(this.reconnectTimer),this.reconnectTimer=null)},T.prototype._cleanUp=function(e,t){const r=arguments[2];if(t&&(y("_cleanUp :: done callback provided for on stream close"),this.stream.on("close",t)),y("_cleanUp :: forced? %s",e),e)0===this.options.reconnectPeriod&&this.options.clean&&(n=this.outgoing)&&(y("flush: queue exists? %b",!!n),Object.keys(n).forEach(function(e){"function"==typeof n[e].cb&&(n[e].cb(new Error("Connection closed")),delete n[e])})),y("_cleanUp :: (%s) :: destroying stream",this.options.clientId),this.stream.destroy();else{const e=g({cmd:"disconnect"},r);y("_cleanUp :: (%s) :: call _sendPacket with disconnect packet",this.options.clientId),this._sendPacket(e,m.bind(null,this.stream.end.bind(this.stream)))}var n;this.disconnecting||(y("_cleanUp :: client not disconnecting. Clearing and resetting reconnect."),this._clearReconnect(),this._setupReconnect()),null!==this.pingTimer&&(y("_cleanUp :: clearing pingTimer"),this.pingTimer.clear(),this.pingTimer=null),t&&!this.connected&&(y("_cleanUp :: (%s) :: removing stream `done` callback `close` listener",this.options.clientId),this.stream.removeListener("close",t),t())},T.prototype._sendPacket=function(e,t,r){y("_sendPacket :: (%s) :: start",this.options.clientId),r=r||C,t=t||C;const n=function(e,t){if(5===e.options.protocolVersion&&"publish"===t.cmd){let r;t.properties&&(r=t.properties.topicAlias);const n=t.topic.toString();if(e.topicAliasSend)if(r){if(0!==n.length&&(y("applyTopicAlias :: register topic: %s - alias: %d",n,r),!e.topicAliasSend.put(n,r)))return y("applyTopicAlias :: error out of range. topic: %s - alias: %d",n,r),new Error("Sending Topic Alias out of range")}else 0!==n.length&&(e.options.autoAssignTopicAlias?(r=e.topicAliasSend.getAliasByTopic(n))?(t.topic="",t.properties={...t.properties,topicAlias:r},y("applyTopicAlias :: auto assign(use) topic: %s - alias: %d",n,r)):(r=e.topicAliasSend.getLruAlias(),e.topicAliasSend.put(n,r),t.properties={...t.properties,topicAlias:r},y("applyTopicAlias :: auto assign topic: %s - alias: %d",n,r)):e.options.autoUseTopicAlias&&(r=e.topicAliasSend.getAliasByTopic(n))&&(t.topic="",t.properties={...t.properties,topicAlias:r},y("applyTopicAlias :: auto use topic: %s - alias: %d",n,r)));else if(r)return y("applyTopicAlias :: error out of range. topic: %s - alias: %d",n,r),new Error("Sending Topic Alias out of range")}}(this,e);if(n)t(n);else{if(!this.connected)return"auth"===e.cmd?(this._shiftPingInterval(),void S(this,e,t)):(y("_sendPacket :: client not connected. Storing packet offline."),void this._storePacket(e,t,r));switch(this._shiftPingInterval(),e.cmd){case"publish":break;case"pubrel":return void E(this,e,t,r);default:return void S(this,e,t)}switch(e.qos){case 2:case 1:E(this,e,t,r);break;case 0:default:S(this,e,t)}y("_sendPacket :: (%s) :: end",this.options.clientId)}},T.prototype._storePacket=function(e,t,r){y("_storePacket :: packet: %o",e),y("_storePacket :: cb? %s",!!t),r=r||C;let n=e;if("publish"===n.cmd){const r=k(this,n=p(e));if(r)return t&&t(r)}0===(n.qos||0)&&this.queueQoSZero||"publish"!==n.cmd?this.queue.push({packet:n,cb:t}):n.qos>0?(t=this.outgoing[n.messageId]?this.outgoing[n.messageId].cb:null,this.outgoingStore.put(n,function(e){if(e)return t&&t(e);r()})):t&&t(new Error("No connection to broker"))},T.prototype._setupPingTimer=function(){y("_setupPingTimer :: keepalive %d (seconds)",this.options.keepalive);const e=this;!this.pingTimer&&this.options.keepalive&&(this.pingResp=!0,this.pingTimer=f(function(){e._checkPing()},1e3*this.options.keepalive))},T.prototype._shiftPingInterval=function(){this.pingTimer&&this.options.keepalive&&this.options.reschedulePings&&this.pingTimer.reschedule(1e3*this.options.keepalive)},T.prototype._checkPing=function(){y("_checkPing :: checking ping..."),this.pingResp?(y("_checkPing :: ping response received. Clearing flag and sending `pingreq`"),this.pingResp=!1,this._sendPacket({cmd:"pingreq"})):(y("_checkPing :: calling _cleanUp with force true"),this._cleanUp(!0))},T.prototype._handlePingresp=function(){this.pingResp=!0},T.prototype._handleConnack=function(e){y("_handleConnack");const t=this.options,r=5===t.protocolVersion?e.reasonCode:e.returnCode;if(clearTimeout(this.connackTimer),delete this.topicAliasSend,e.properties){if(e.properties.topicAliasMaximum){if(e.properties.topicAliasMaximum>65535)return void this.emit("error",new Error("topicAliasMaximum from broker is out of range"));e.properties.topicAliasMaximum>0&&(this.topicAliasSend=new a(e.properties.topicAliasMaximum))}e.properties.serverKeepAlive&&t.keepalive&&(t.keepalive=e.properties.serverKeepAlive,this._shiftPingInterval()),e.properties.maximumPacketSize&&(t.properties||(t.properties={}),t.properties.maximumPacketSize=e.properties.maximumPacketSize)}if(0===r)this.reconnecting=!1,this._onConnect(e);else if(r>0){const e=new Error("Connection refused: "+_[r]);e.code=r,this.emit("error",e)}},T.prototype._handleAuth=function(e){const t=this.options.protocolVersion,r=5===t?e.reasonCode:e.returnCode;if(5!==t){const e=new Error("Protocol error: Auth packets are only supported in MQTT 5. Your version:"+t);return e.code=r,void this.emit("error",e)}const n=this;this.handleAuth(e,function(e,t){if(e)n.emit("error",e);else if(24===r)n.reconnecting=!1,n._sendPacket(t);else{const t=new Error("Connection refused: "+_[r]);e.code=r,n.emit("error",t)}})},T.prototype.handleAuth=function(e,t){t()},T.prototype._handlePublish=function(e,t){y("_handlePublish: packet %o",e),t=void 0!==t?t:C;let r=e.topic.toString();const n=e.payload,i=e.qos,o=e.messageId,s=this,a=this.options,l=[0,16,128,131,135,144,145,151,153];if(5===this.options.protocolVersion){let t;if(e.properties&&(t=e.properties.topicAlias),void 0!==t)if(0===r.length){if(!(t>0&&t<=65535))return y("_handlePublish :: topic alias out of range. alias: %d",t),void this.emit("error",new Error("Received Topic Alias is out of range"));{const e=this.topicAliasRecv.getTopicByAlias(t);if(!e)return y("_handlePublish :: unregistered topic alias. alias: %d",t),void this.emit("error",new Error("Received unregistered Topic Alias"));y("_handlePublish :: topic complemented by alias. topic: %s - alias: %d",r=e,t)}}else{if(!this.topicAliasRecv.put(r,t))return y("_handlePublish :: topic alias out of range. alias: %d",t),void this.emit("error",new Error("Received Topic Alias is out of range"));y("_handlePublish :: registered topic: %s - alias: %d",r,t)}}switch(y("_handlePublish: qos %d",i),i){case 2:a.customHandleAcks(r,n,e,function(r,n){return r instanceof Error||(n=r,r=null),r?s.emit("error",r):-1===l.indexOf(n)?s.emit("error",new Error("Wrong reason code for pubrec")):void(n?s._sendPacket({cmd:"pubrec",messageId:o,reasonCode:n},t):s.incomingStore.put(e,function(){s._sendPacket({cmd:"pubrec",messageId:o},t)}))});break;case 1:a.customHandleAcks(r,n,e,function(i,a){return i instanceof Error||(a=i,i=null),i?s.emit("error",i):-1===l.indexOf(a)?s.emit("error",new Error("Wrong reason code for puback")):(a||s.emit("message",r,n,e),void s.handleMessage(e,function(e){if(e)return t&&t(e);s._sendPacket({cmd:"puback",messageId:o,reasonCode:a},t)}))});break;case 0:this.emit("message",r,n,e),this.handleMessage(e,t);break;default:y("_handlePublish: unknown QoS. Doing nothing.")}},T.prototype.handleMessage=function(e,t){t()},T.prototype._handleAck=function(e){const t=e.messageId,r=e.cmd;let n=null;const i=this.outgoing[t]?this.outgoing[t].cb:null,o=this;let s;if(i){switch(y("_handleAck :: packet type",r),r){case"pubcomp":case"puback":{const r=e.reasonCode;r&&r>0&&16!==r&&((s=new Error("Publish error: "+_[r])).code=r,i(s,e)),delete this.outgoing[t],this.outgoingStore.del(e,i),this.messageIdProvider.deallocate(t),this._invokeStoreProcessingQueue();break}case"pubrec":{n={cmd:"pubrel",qos:2,messageId:t};const r=e.reasonCode;r&&r>0&&16!==r?((s=new Error("Publish error: "+_[r])).code=r,i(s,e)):this._sendPacket(n);break}case"suback":delete this.outgoing[t],this.messageIdProvider.deallocate(t);for(let r=0;r0)if(this.options.resubscribe)if(5===this.options.protocolVersion){y("_resubscribe: protocolVersion 5");for(let t=0;t0){const e=this._storeProcessingQueue[0];if(e&&e.invoke())return this._storeProcessingQueue.shift(),!0}return!1},T.prototype._invokeAllStoreProcessingQueue=function(){for(;this._invokeStoreProcessingQueue(););},T.prototype._flushStoreProcessingQueue=function(){for(const e of this._storeProcessingQueue)e.cbStorePut&&e.cbStorePut(new Error("Connection closed")),e.callback&&e.callback(new Error("Connection closed"));this._storeProcessingQueue.splice(0)},t.exports=T}).call(this)}).call(this,e("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./default-message-id-provider":7,"./store":8,"./topic-alias-recv":9,"./topic-alias-send":10,"./validations":11,_process:50,debug:18,events:22,inherits:24,"mqtt-packet":40,"readable-stream":69,reinterval:70,"rfdc/default":71,xtend:81}],2:[function(e,t,r){"use strict";const{Buffer:n}=e("buffer"),i=e("readable-stream").Transform,o=e("duplexify");let s,a,l,u=!1;t.exports=function(e,t){if(t.hostname=t.hostname||t.host,!t.hostname)throw new Error("Could not determine host. Specify host manually.");const r="MQIsdp"===t.protocolId&&3===t.protocolVersion?"mqttv3.1":"mqtt";!function(e){e.hostname||(e.hostname="localhost"),e.path||(e.path="/"),e.wsOptions||(e.wsOptions={})}(t);const c=function(e,t){const r="alis"===e.protocol?"wss":"ws";let n=r+"://"+e.hostname+e.path;return e.port&&80!==e.port&&443!==e.port&&(n=r+"://"+e.hostname+":"+e.port+e.path),"function"==typeof e.transformWsUrl&&(n=e.transformWsUrl(n,e,t)),n}(t,e);return(s=t.my).connectSocket({url:c,protocols:r}),a=function(){const e=new i;return e._write=function(e,t,r){s.sendSocketMessage({data:e.buffer,success:function(){r()},fail:function(){r(new Error)}})},e._flush=function(e){s.closeSocket({success:function(){e()}})},e}(),l=o.obj(),u||(u=!0,s.onSocketOpen(function(){l.setReadable(a),l.setWritable(a),l.emit("connect")}),s.onSocketMessage(function(e){if("string"==typeof e.data){const t=n.from(e.data,"base64");a.push(t)}else{const t=new FileReader;t.addEventListener("load",function(){let e=t.result;e=e instanceof ArrayBuffer?n.from(e):n.from(e,"utf8"),a.push(e)}),t.readAsArrayBuffer(e.data)}}),s.onSocketClose(function(){l.end(),l.destroy()}),s.onSocketError(function(e){l.destroy(e)})),l}},{buffer:17,duplexify:20,"readable-stream":69}],3:[function(e,t,r){"use strict";const n=e("net"),i=e("debug")("mqttjs:tcp");t.exports=function(e,t){t.port=t.port||1883,t.hostname=t.hostname||t.host||"localhost";const r=t.port,o=t.hostname;return i("port %d and host %s",r,o),n.createConnection(r,o)}},{debug:18,net:16}],4:[function(e,t,r){"use strict";const n=e("tls"),i=e("net"),o=e("debug")("mqttjs:tls");t.exports=function(e,t){t.port=t.port||8883,t.host=t.hostname||t.host||"localhost",0===i.isIP(t.host)&&(t.servername=t.host),t.rejectUnauthorized=!1!==t.rejectUnauthorized,delete t.path,o("port %d host %s rejectUnauthorized %b",t.port,t.host,t.rejectUnauthorized);const r=n.connect(t);function s(n){t.rejectUnauthorized&&e.emit("error",n),r.end()}return r.on("secureConnect",function(){t.rejectUnauthorized&&!r.authorized?r.emit("error",new Error("TLS not authorized")):r.removeListener("error",s)}),r.on("error",s),r}},{debug:18,net:16,tls:16}],5:[function(e,t,r){(function(r){(function(){"use strict";const{Buffer:n}=e("buffer"),i=e("ws"),o=e("debug")("mqttjs:ws"),s=e("duplexify"),a=e("readable-stream").Transform,l=["rejectUnauthorized","ca","cert","key","pfx","passphrase"],u=void 0!==r&&"browser"===r.title||"function"==typeof __webpack_require__;function c(e,t){let r=e.protocol+"://"+e.hostname+":"+e.port+e.path;return"function"==typeof e.transformWsUrl&&(r=e.transformWsUrl(r,e,t)),r}function h(e){const t=e;return e.hostname||(t.hostname="localhost"),e.port||("wss"===e.protocol?t.port=443:t.port=80),e.path||(t.path="/"),e.wsOptions||(t.wsOptions={}),u||"wss"!==e.protocol||l.forEach(function(r){Object.prototype.hasOwnProperty.call(e,r)&&!Object.prototype.hasOwnProperty.call(e.wsOptions,r)&&(t.wsOptions[r]=e[r])}),t}t.exports=u?function(e,t){let r;o("browserStreamBuilder");const i=function(e){const t=h(e);if(t.hostname||(t.hostname=t.host),!t.hostname){if("undefined"==typeof document)throw new Error("Could not determine host. Specify host manually.");const e=new URL(document.URL);t.hostname=e.hostname,t.port||(t.port=e.port)}return void 0===t.objectMode&&(t.objectMode=!(!0===t.binary||void 0===t.binary)),t}(t).browserBufferSize||524288,l=t.browserBufferTimeout||1e3,u=!t.objectMode,f=function(e,t){const r="MQIsdp"===t.protocolId&&3===t.protocolVersion?"mqttv3.1":"mqtt",n=c(t,e),i=new WebSocket(n,[r]);return i.binaryType="arraybuffer",i}(e,t),p=function(e,t,r){const n=new a({objectModeMode:e.objectMode});return n._write=t,n._flush=r,n}(t,function e(t,r,o){f.bufferedAmount>i&&setTimeout(e,l,t,r,o),u&&"string"==typeof t&&(t=n.from(t,"utf8"));try{f.send(t)}catch(e){return o(e)}o()},function(e){f.close(),e()});t.objectMode||(p._writev=v),p.on("close",()=>{f.close()});const d=void 0!==f.addEventListener;function g(){r.setReadable(p),r.setWritable(p),r.emit("connect")}function y(){r.end(),r.destroy()}function b(e){r.destroy(e)}function m(e){let t=e.data;t=t instanceof ArrayBuffer?n.from(t):n.from(t,"utf8"),p.push(t)}function v(e,t){const r=new Array(e.length);for(let t=0;t{a.destroy()}),a}}).call(this)}).call(this,e("_process"))},{_process:50,buffer:17,debug:18,duplexify:20,"readable-stream":69,ws:80}],6:[function(e,t,r){"use strict";const{Buffer:n}=e("buffer"),i=e("readable-stream").Transform,o=e("duplexify");let s,a,l;t.exports=function(e,t){if(t.hostname=t.hostname||t.host,!t.hostname)throw new Error("Could not determine host. Specify host manually.");const r="MQIsdp"===t.protocolId&&3===t.protocolVersion?"mqttv3.1":"mqtt";!function(e){e.hostname||(e.hostname="localhost"),e.path||(e.path="/"),e.wsOptions||(e.wsOptions={})}(t);const u=function(e,t){const r="wxs"===e.protocol?"wss":"ws";let n=r+"://"+e.hostname+e.path;return e.port&&80!==e.port&&443!==e.port&&(n=r+"://"+e.hostname+":"+e.port+e.path),"function"==typeof e.transformWsUrl&&(n=e.transformWsUrl(n,e,t)),n}(t,e);s=wx.connectSocket({url:u,protocols:[r]}),a=function(){const e=new i;return e._write=function(e,t,r){s.send({data:e.buffer,success:function(){r()},fail:function(e){r(new Error(e))}})},e._flush=function(e){s.close({success:function(){e()}})},e}(),(l=o.obj())._destroy=function(e,t){s.close({success:function(){t&&t(e)}})};const c=l.destroy;return l.destroy=function(){l.destroy=c;const e=this;setTimeout(function(){s.close({fail:function(){e._destroy(new Error)}})},0)}.bind(l),s.onOpen(function(){l.setReadable(a),l.setWritable(a),l.emit("connect")}),s.onMessage(function(e){let t=e.data;t=t instanceof ArrayBuffer?n.from(t):n.from(t,"utf8"),a.push(t)}),s.onClose(function(){l.end(),l.destroy()}),s.onError(function(e){l.destroy(new Error(e.errMsg))}),l}},{buffer:17,duplexify:20,"readable-stream":69}],7:[function(e,t,r){"use strict";function n(){if(!(this instanceof n))return new n;this.nextId=Math.max(1,Math.floor(65535*Math.random()))}n.prototype.allocate=function(){const e=this.nextId++;return 65536===this.nextId&&(this.nextId=1),e},n.prototype.getLastAllocated=function(){return 1===this.nextId?65535:this.nextId-1},n.prototype.register=function(e){return!0},n.prototype.deallocate=function(e){},n.prototype.clear=function(){},t.exports=n},{}],8:[function(e,t,r){"use strict";const n=e("xtend"),i=e("readable-stream").Readable,o={objectMode:!0},s={clean:!0};function a(e){if(!(this instanceof a))return new a(e);this.options=e||{},this.options=n(s,e),this._inflights=new Map}a.prototype.put=function(e,t){return this._inflights.set(e.messageId,e),t&&t(),this},a.prototype.createStream=function(){const e=new i(o),t=[];let r=!1,n=0;return this._inflights.forEach(function(e,r){t.push(e)}),e._read=function(){!r&&nthis.max)&&(this.aliasToTopic[t]=e,this.length=Object.keys(this.aliasToTopic).length,!0)},n.prototype.getTopicByAlias=function(e){return this.aliasToTopic[e]},n.prototype.clear=function(){this.aliasToTopic={}},t.exports=n},{}],10:[function(e,t,r){"use strict";const n=e("lru-cache"),i=e("number-allocator").NumberAllocator;function o(e){if(!(this instanceof o))return new o(e);e>0&&(this.aliasToTopic=new n({max:e}),this.topicToAlias={},this.numberAllocator=new i(1,e),this.max=e,this.length=0)}o.prototype.put=function(e,t){if(0===t||t>this.max)return!1;const r=this.aliasToTopic.get(t);return r&&delete this.topicToAlias[r],this.aliasToTopic.set(t,e),this.topicToAlias[e]=t,this.numberAllocator.use(t),this.length=this.aliasToTopic.length,!0},o.prototype.getTopicByAlias=function(e){return this.aliasToTopic.get(e)},o.prototype.getAliasByTopic=function(e){const t=this.topicToAlias[e];return void 0!==t&&this.aliasToTopic.get(t),t},o.prototype.clear=function(){this.aliasToTopic.reset(),this.topicToAlias={},this.numberAllocator.clear(),this.length=0},o.prototype.getLruAlias=function(){const e=this.numberAllocator.firstVacant();return e||this.aliasToTopic.keys()[this.aliasToTopic.length-1]},t.exports=o},{"lru-cache":37,"number-allocator":46}],11:[function(e,t,r){"use strict";function n(e){const t=e.split("/");for(let e=0;e0?s-4:s;for(r=0;r>16&255,l[c++]=t>>8&255,l[c++]=255&t;2===a&&(t=i[e.charCodeAt(r)]<<2|i[e.charCodeAt(r+1)]>>4,l[c++]=255&t);1===a&&(t=i[e.charCodeAt(r)]<<10|i[e.charCodeAt(r+1)]<<4|i[e.charCodeAt(r+2)]>>2,l[c++]=t>>8&255,l[c++]=255&t);return l},r.fromByteArray=function(e){for(var t,r=e.length,i=r%3,o=[],s=0,a=r-i;sa?a:s+16383));1===i?(t=e[r-1],o.push(n[t>>2]+n[t<<4&63]+"==")):2===i&&(t=(e[r-2]<<8)+e[r-1],o.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return o.join("")};for(var n=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,l=s.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");return-1===r&&(r=t),[r,r===t?0:4-r%4]}function c(e,t,r){for(var i,o,s=[],a=t;a>18&63]+n[o>>12&63]+n[o>>6&63]+n[63&o]);return s.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},{}],14:[function(e,t,r){"use strict";const{Buffer:n}=e("buffer"),i=Symbol.for("BufferList");function o(e){if(!(this instanceof o))return new o(e);o._init.call(this,e)}o._init=function(e){Object.defineProperty(this,i,{value:!0}),this._bufs=[],this.length=0,e&&this.append(e)},o.prototype._new=function(e){return new o(e)},o.prototype._offset=function(e){if(0===e)return[0,0];let t=0;for(let r=0;rthis.length||e<0)return;const t=this._offset(e);return this._bufs[t[0]][t[1]]},o.prototype.slice=function(e,t){return"number"==typeof e&&e<0&&(e+=this.length),"number"==typeof t&&t<0&&(t+=this.length),this.copy(null,0,e,t)},o.prototype.copy=function(e,t,r,i){if(("number"!=typeof r||r<0)&&(r=0),("number"!=typeof i||i>this.length)&&(i=this.length),r>=this.length)return e||n.alloc(0);if(i<=0)return e||n.alloc(0);const o=!!e,s=this._offset(r),a=i-r;let l=a,u=o&&t||0,c=s[1];if(0===r&&i===this.length){if(!o)return 1===this._bufs.length?this._bufs[0]:n.concat(this._bufs,this.length);for(let t=0;tr)){this._bufs[t].copy(e,u,c,c+l),u+=r;break}this._bufs[t].copy(e,u,c),u+=r,l-=r,c&&(c=0)}return e.length>u?e.slice(0,u):e},o.prototype.shallowSlice=function(e,t){if(e=e||0,t="number"!=typeof t?this.length:t,e<0&&(e+=this.length),t<0&&(t+=this.length),e===t)return this._new();const r=this._offset(e),n=this._offset(t),i=this._bufs.slice(r[0],n[0]+1);return 0===n[1]?i.pop():i[i.length-1]=i[i.length-1].slice(0,n[1]),0!==r[1]&&(i[0]=i[0].slice(r[1])),this._new(i)},o.prototype.toString=function(e,t,r){return this.slice(t,r).toString(e)},o.prototype.consume=function(e){if(e=Math.trunc(e),Number.isNaN(e)||e<=0)return this;for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},o.prototype.duplicate=function(){const e=this._new();for(let t=0;tthis.length?this.length:t;const i=this._offset(t);let o=i[0],s=i[1];for(;o=e.length){const r=t.indexOf(e,s);if(-1!==r)return this._reverseOffset([o,r]);s=t.length-e.length+1}else{const t=this._reverseOffset([o,s]);if(this._match(t,e))return t;s++}}s=0}return-1},o.prototype._match=function(e,t){if(this.length-ei)throw new RangeError('The value "'+e+'" is invalid for option "size"');var t=new Uint8Array(e);return t.__proto__=s.prototype,t}function s(e,t,r){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return u(e)}return a(e,t,r)}function a(e,t,r){if("string"==typeof e)return function(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!s.isEncoding(t))throw new TypeError("Unknown encoding: "+t);var r=0|f(e,t),n=o(r),i=n.write(e,t);i!==r&&(n=n.slice(0,i));return n}(e,t);if(ArrayBuffer.isView(e))return c(e);if(null==e)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(q(e,ArrayBuffer)||e&&q(e.buffer,ArrayBuffer))return function(e,t,r){if(t<0||e.byteLength=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return 0|e}function f(e,t){if(s.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||q(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var r=e.length,n=arguments.length>2&&!0===arguments[2];if(!n&&0===r)return 0;for(var i=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":return L(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return j(e).length;default:if(i)return n?-1:L(e).length;t=(""+t).toLowerCase(),i=!0}}function p(e,t,r){var n=e[t];e[t]=e[r],e[r]=n}function d(e,t,r,n,i){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),D(r=+r)&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(i)return-1;r=e.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof t&&(t=s.from(t,n)),s.isBuffer(t))return 0===t.length?-1:g(e,t,r,n,i);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):g(e,[t],r,n,i);throw new TypeError("val must be string, number or Buffer")}function g(e,t,r,n,i){var o,s=1,a=e.length,l=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s=2,a/=2,l/=2,r/=2}function u(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(i){var c=-1;for(o=r;oa&&(r=a-l),o=r;o>=0;o--){for(var h=!0,f=0;fi&&(n=i):n=i;var o=t.length;n>o/2&&(n=o/2);for(var s=0;s>8,i=r%256,o.push(i),o.push(n);return o}(t,e.length-r),e,r,n)}function k(e,r,n){return 0===r&&n===e.length?t.fromByteArray(e):t.fromByteArray(e.slice(r,n))}function S(e,t,r){r=Math.min(e.length,r);for(var n=[],i=t;i239?4:u>223?3:u>191?2:1;if(i+h<=r)switch(h){case 1:u<128&&(c=u);break;case 2:128==(192&(o=e[i+1]))&&(l=(31&u)<<6|63&o)>127&&(c=l);break;case 3:o=e[i+1],s=e[i+2],128==(192&o)&&128==(192&s)&&(l=(15&u)<<12|(63&o)<<6|63&s)>2047&&(l<55296||l>57343)&&(c=l);break;case 4:o=e[i+1],s=e[i+2],a=e[i+3],128==(192&o)&&128==(192&s)&&128==(192&a)&&(l=(15&u)<<18|(63&o)<<12|(63&s)<<6|63&a)>65535&&l<1114112&&(c=l)}null===c?(c=65533,h=1):c>65535&&(c-=65536,n.push(c>>>10&1023|55296),c=56320|1023&c),n.push(c),i+=h}return function(e){var t=e.length;if(t<=E)return String.fromCharCode.apply(String,e);var r="",n=0;for(;nthis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return x(this,t,r);case"utf8":case"utf-8":return S(this,t,r);case"ascii":return C(this,t,r);case"latin1":case"binary":return T(this,t,r);case"base64":return k(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return A(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}.apply(this,arguments)},s.prototype.toLocaleString=s.prototype.toString,s.prototype.equals=function(e){if(!s.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===s.compare(this,e)},s.prototype.inspect=function(){var e="",t=r.INSPECT_MAX_BYTES;return e=this.toString("hex",0,t).replace(/(.{2})/g,"$1 ").trim(),this.length>t&&(e+=" ... "),""},s.prototype.compare=function(e,t,r,n,i){if(q(e,Uint8Array)&&(e=s.from(e,e.offset,e.byteLength)),!s.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw new RangeError("out of range index");if(n>=i&&t>=r)return 0;if(n>=i)return-1;if(t>=r)return 1;if(t>>>=0,r>>>=0,n>>>=0,i>>>=0,this===e)return 0;for(var o=i-n,a=r-t,l=Math.min(o,a),u=this.slice(n,i),c=e.slice(t,r),h=0;h>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0)}var i=this.length-t;if((void 0===r||r>i)&&(r=i),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return y(this,e,t,r);case"utf8":case"utf-8":return b(this,e,t,r);case"ascii":return m(this,e,t,r);case"latin1":case"binary":return v(this,e,t,r);case"base64":return w(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return _(this,e,t,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},s.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var E=4096;function C(e,t,r){var n="";r=Math.min(e.length,r);for(var i=t;in)&&(r=n);for(var i="",o=t;or)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,r,n,i,o){if(!s.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw new RangeError("Index out of range")}function O(e,t,r,n,i,o){if(r+n>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function B(e,t,r,i,o){return t=+t,r>>>=0,o||O(e,0,r,4),n.write(e,t,r,i,23,4),r+4}function R(e,t,r,i,o){return t=+t,r>>>=0,o||O(e,0,r,8),n.write(e,t,r,i,52,8),r+8}s.prototype.slice=function(e,t){var r=this.length;e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t>>=0,t>>>=0,r||I(e,t,this.length);for(var n=this[e],i=1,o=0;++o>>=0,t>>>=0,r||I(e,t,this.length);for(var n=this[e+--t],i=1;t>0&&(i*=256);)n+=this[e+--t]*i;return n},s.prototype.readUInt8=function(e,t){return e>>>=0,t||I(e,1,this.length),this[e]},s.prototype.readUInt16LE=function(e,t){return e>>>=0,t||I(e,2,this.length),this[e]|this[e+1]<<8},s.prototype.readUInt16BE=function(e,t){return e>>>=0,t||I(e,2,this.length),this[e]<<8|this[e+1]},s.prototype.readUInt32LE=function(e,t){return e>>>=0,t||I(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},s.prototype.readUInt32BE=function(e,t){return e>>>=0,t||I(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},s.prototype.readIntLE=function(e,t,r){e>>>=0,t>>>=0,r||I(e,t,this.length);for(var n=this[e],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*t)),n},s.prototype.readIntBE=function(e,t,r){e>>>=0,t>>>=0,r||I(e,t,this.length);for(var n=t,i=1,o=this[e+--n];n>0&&(i*=256);)o+=this[e+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},s.prototype.readInt8=function(e,t){return e>>>=0,t||I(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},s.prototype.readInt16LE=function(e,t){e>>>=0,t||I(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},s.prototype.readInt16BE=function(e,t){e>>>=0,t||I(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},s.prototype.readInt32LE=function(e,t){return e>>>=0,t||I(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},s.prototype.readInt32BE=function(e,t){return e>>>=0,t||I(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},s.prototype.readFloatLE=function(e,t){return e>>>=0,t||I(e,4,this.length),n.read(this,e,!0,23,4)},s.prototype.readFloatBE=function(e,t){return e>>>=0,t||I(e,4,this.length),n.read(this,e,!1,23,4)},s.prototype.readDoubleLE=function(e,t){return e>>>=0,t||I(e,8,this.length),n.read(this,e,!0,52,8)},s.prototype.readDoubleBE=function(e,t){return e>>>=0,t||I(e,8,this.length),n.read(this,e,!1,52,8)},s.prototype.writeUIntLE=function(e,t,r,n){(e=+e,t>>>=0,r>>>=0,n)||P(this,e,t,r,Math.pow(2,8*r)-1,0);var i=1,o=0;for(this[t]=255&e;++o>>=0,r>>>=0,n)||P(this,e,t,r,Math.pow(2,8*r)-1,0);var i=r-1,o=1;for(this[t+i]=255&e;--i>=0&&(o*=256);)this[t+i]=e/o&255;return t+r},s.prototype.writeUInt8=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,1,255,0),this[t]=255&e,t+1},s.prototype.writeUInt16LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},s.prototype.writeUInt16BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},s.prototype.writeUInt32LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},s.prototype.writeUInt32BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},s.prototype.writeIntLE=function(e,t,r,n){if(e=+e,t>>>=0,!n){var i=Math.pow(2,8*r-1);P(this,e,t,r,i-1,-i)}var o=0,s=1,a=0;for(this[t]=255&e;++o>0)-a&255;return t+r},s.prototype.writeIntBE=function(e,t,r,n){if(e=+e,t>>>=0,!n){var i=Math.pow(2,8*r-1);P(this,e,t,r,i-1,-i)}var o=r-1,s=1,a=0;for(this[t+o]=255&e;--o>=0&&(s*=256);)e<0&&0===a&&0!==this[t+o+1]&&(a=1),this[t+o]=(e/s>>0)-a&255;return t+r},s.prototype.writeInt8=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},s.prototype.writeInt16LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},s.prototype.writeInt16BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},s.prototype.writeInt32LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},s.prototype.writeInt32BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},s.prototype.writeFloatLE=function(e,t,r){return B(this,e,t,!0,r)},s.prototype.writeFloatBE=function(e,t,r){return B(this,e,t,!1,r)},s.prototype.writeDoubleLE=function(e,t,r){return R(this,e,t,!0,r)},s.prototype.writeDoubleBE=function(e,t,r){return R(this,e,t,!1,r)},s.prototype.copy=function(e,t,r,n){if(!s.isBuffer(e))throw new TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t=0;--o)e[o+t]=this[o+r];else Uint8Array.prototype.set.call(e,this.subarray(r,n),t);return i},s.prototype.fill=function(e,t,r,n){if("string"==typeof e){if("string"==typeof t?(n=t,t=0,r=this.length):"string"==typeof r&&(n=r,r=this.length),void 0!==n&&"string"!=typeof n)throw new TypeError("encoding must be a string");if("string"==typeof n&&!s.isEncoding(n))throw new TypeError("Unknown encoding: "+n);if(1===e.length){var i=e.charCodeAt(0);("utf8"===n&&i<128||"latin1"===n)&&(e=i)}}else"number"==typeof e&&(e&=255);if(t<0||this.length>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(o=t;o55295&&r<57344){if(!i){if(r>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(s+1===n){(t-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(t-=3)>-1&&o.push(239,191,189),i=r;continue}r=65536+(i-55296<<10|r-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function j(e){return t.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(M,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function U(e,t,r,n){for(var i=0;i=t.length||i>=e.length);++i)t[i+r]=e[i];return i}function q(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function D(e){return e!=e}}).call(this)}).call(this,e("buffer").Buffer)},{"base64-js":13,buffer:17,ieee754:23}],18:[function(e,t,r){(function(n){(function(){r.formatArgs=function(e){if(e[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+e[0]+(this.useColors?"%c ":" ")+"+"+t.exports.humanize(this.diff),!this.useColors)return;const r="color: "+this.color;e.splice(1,0,r,"color: inherit");let n=0,i=0;e[0].replace(/%[a-zA-Z%]/g,e=>{"%%"!==e&&"%c"===e&&(i=++n)}),e.splice(i,0,r)},r.save=function(e){try{e?r.storage.setItem("debug",e):r.storage.removeItem("debug")}catch(e){}},r.load=function(){let e;try{e=r.storage.getItem("debug")}catch(e){}!e&&void 0!==n&&"env"in n&&(e=n.env.DEBUG);return e},r.useColors=function(){if("undefined"!=typeof window&&window.process&&("renderer"===window.process.type||window.process.__nwjs))return!0;if("undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))return!1;return"undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},r.storage=function(){try{return localStorage}catch(e){}}(),r.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),r.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],r.log=console.debug||console.log||(()=>{}),t.exports=e("./common")(r);const{formatters:i}=t.exports;i.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}}).call(this)}).call(this,e("_process"))},{"./common":19,_process:50}],19:[function(e,t,r){t.exports=function(t){function r(e){let t,i,o,s=null;function a(...e){if(!a.enabled)return;const n=a,i=Number(new Date),o=i-(t||i);n.diff=o,n.prev=t,n.curr=i,t=i,e[0]=r.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let s=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,(t,i)=>{if("%%"===t)return"%";s++;const o=r.formatters[i];if("function"==typeof o){const r=e[s];t=o.call(n,r),e.splice(s,1),s--}return t}),r.formatArgs.call(n,e),(n.log||r.log).apply(n,e)}return a.namespace=e,a.useColors=r.useColors(),a.color=r.selectColor(e),a.extend=n,a.destroy=r.destroy,Object.defineProperty(a,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==s?s:(i!==r.namespaces&&(i=r.namespaces,o=r.enabled(e)),o),set:e=>{s=e}}),"function"==typeof r.init&&r.init(a),a}function n(e,t){const n=r(this.namespace+(void 0===t?":":t)+e);return n.log=this.log,n}function i(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return r.debug=r,r.default=r,r.coerce=function(e){return e instanceof Error?e.stack||e.message:e},r.disable=function(){const e=[...r.names.map(i),...r.skips.map(i).map(e=>"-"+e)].join(",");return r.enable(""),e},r.enable=function(e){let t;r.save(e),r.namespaces=e,r.names=[],r.skips=[];const n=("string"==typeof e?e:"").split(/[\s,]+/),i=n.length;for(t=0;t{r[e]=t[e]}),r.names=[],r.skips=[],r.formatters={},r.selectColor=function(e){let t=0;for(let r=0;r0&&a.length>o){a.warned=!0;var l=new Error("Possible EventEmitter memory leak detected. "+a.length+' "'+String(t)+'" listeners added. Use emitter.setMaxListeners() to increase limit.');l.name="MaxListenersExceededWarning",l.emitter=e,l.type=t,l.count=a.length,"object"==typeof console&&console.warn&&console.warn("%s: %s",l.name,l.message)}}else a=s[t]=r,++e._eventsCount;return e}function f(){if(!this.fired)switch(this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length){case 0:return this.listener.call(this.target);case 1:return this.listener.call(this.target,arguments[0]);case 2:return this.listener.call(this.target,arguments[0],arguments[1]);case 3:return this.listener.call(this.target,arguments[0],arguments[1],arguments[2]);default:for(var e=new Array(arguments.length),t=0;t1&&(t=arguments[1]),t instanceof Error)throw t;var l=new Error('Unhandled "error" event. ('+t+")");throw l.context=t,l}if(!(r=s[e]))return!1;var u="function"==typeof r;switch(n=arguments.length){case 1:!function(e,t,r){if(t)e.call(r);else for(var n=e.length,i=y(e,n),o=0;o=0;s--)if(r[s]===t||r[s].listener===t){a=r[s].listener,o=s;break}if(o<0)return this;0===o?r.shift():function(e,t){for(var r=t,n=r+1,i=e.length;n=0;o--)this.removeListener(e,t[o]);return this},s.prototype.listeners=function(e){return d(this,e,!0)},s.prototype.rawListeners=function(e){return d(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):g.call(e,t)},s.prototype.listenerCount=g,s.prototype.eventNames=function(){return this._eventsCount>0?Reflect.ownKeys(this._events):[]}},{}],23:[function(e,t,r){r.read=function(e,t,r,n,i){var o,s,a=8*i-n-1,l=(1<>1,c=-7,h=r?i-1:0,f=r?-1:1,p=e[t+h];for(h+=f,o=p&(1<<-c)-1,p>>=-c,c+=a;c>0;o=256*o+e[t+h],h+=f,c-=8);for(s=o&(1<<-c)-1,o>>=-c,c+=n;c>0;s=256*s+e[t+h],h+=f,c-=8);if(0===o)o=1-u;else{if(o===l)return s?NaN:1/0*(p?-1:1);s+=Math.pow(2,n),o-=u}return(p?-1:1)*s*Math.pow(2,o-n)},r.write=function(e,t,r,n,i,o){var s,a,l,u=8*o-i-1,c=(1<>1,f=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=n?0:o-1,d=n?1:-1,g=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=c):(s=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-s))<1&&(s--,l*=2),(t+=s+h>=1?f/l:f*Math.pow(2,1-h))*l>=2&&(s++,l/=2),s+h>=c?(a=0,s=c):s+h>=1?(a=(t*l-1)*Math.pow(2,i),s+=h):(a=t*Math.pow(2,h-1)*Math.pow(2,i),s=0));i>=8;e[r+p]=255&a,p+=d,a/=256,i-=8);for(s=s<0;e[r+p]=255&s,p+=d,s/=256,u-=8);e[r+p-d]|=128*g}},{}],24:[function(e,t,r){"function"==typeof Object.create?t.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:t.exports=function(e,t){if(t){e.super_=t;var r=function(){};r.prototype=t.prototype,e.prototype=new r,e.prototype.constructor=e}}},{}],25:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n=function(){function e(e,t){this.color=!0,this.key=void 0,this.value=void 0,this.parent=void 0,this.brother=void 0,this.leftChild=void 0,this.rightChild=void 0,this.key=e,this.value=t}return e.prototype.rotateLeft=function(){var e=this.parent,t=this.brother,r=this.leftChild,n=this.rightChild;if(!n)throw new Error("unknown error");var i=n.leftChild,o=n.rightChild;return e&&(e.leftChild===this?e.leftChild=n:e.rightChild===this&&(e.rightChild=n)),n.parent=e,n.brother=t,n.leftChild=this,n.rightChild=o,t&&(t.brother=n),this.parent=n,this.brother=o,this.leftChild=r,this.rightChild=i,o&&(o.parent=n,o.brother=this),r&&(r.parent=this,r.brother=i),i&&(i.parent=this,i.brother=r),n},e.prototype.rotateRight=function(){var e=this.parent,t=this.brother,r=this.leftChild;if(!r)throw new Error("unknown error");var n=this.rightChild,i=r.leftChild,o=r.rightChild;return e&&(e.leftChild===this?e.leftChild=r:e.rightChild===this&&(e.rightChild=r)),r.parent=e,r.brother=t,r.leftChild=i,r.rightChild=this,t&&(t.brother=r),i&&(i.parent=r,i.brother=this),this.parent=r,this.brother=i,this.leftChild=o,this.rightChild=n,o&&(o.parent=this,o.brother=n),n&&(n.parent=this,n.brother=o),r},e.prototype.remove=function(){if(this.leftChild||this.rightChild)throw new Error("can only remove leaf node");this.parent&&(this===this.parent.leftChild?this.parent.leftChild=void 0:this===this.parent.rightChild&&(this.parent.rightChild=void 0)),this.brother&&(this.brother.brother=void 0),this.key=void 0,this.value=void 0,this.parent=void 0,this.brother=void 0},e.TreeNodeColorType={red:!0,black:!1},e}();Object.freeze(n),r.default=n},{}],26:[function(e,t,r){"use strict";var n=this&&this.__generator||function(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]n)throw new Error("pos should more than 0 and less than queue's size");return{curNodeBucketIndex:Math.floor(r/i.bucketSize),curNodePointerIndex:r%i.bucketSize}};this.getElementByPos=function(e){var t=h(e),n=t.curNodeBucketIndex,i=t.curNodePointerIndex;return r[n][i]},this.eraseElementByPos=function(e){var t=this;if(e<0||e>c)throw new Error("pos should more than 0 and less than queue's size");if(0===e)this.popFront();else if(e===this.size())this.popBack();else{for(var r=[],n=e+1;n0?--l:o0&&--c)},this.setElementByPos=function(e,t){var n=h(e),i=n.curNodeBucketIndex,o=n.curNodePointerIndex;r[i][o]=t},this.insert=function(e,t,r){var n=this;if(void 0===r&&(r=1),0===e)for(;r--;)this.pushFront(t);else if(e===this.size())for(;r--;)this.pushBack(t);else{for(var i=[],o=e;o0?--s:o>0&&(--o,s=i.bucketSize-1)),++c,r[o][s]=e},this.popFront=function(){this.empty()||(1!==this.size()&&(s0&&--c)},this.shrinkToFit=function(){var e=this,t=[];this.forEach(function(e){t.push(e)});var n=t.length;r=[];for(var o=Math.ceil(n/i.bucketSize),s=0;s0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(r,"__esModule",{value:!0});var o=e("../LinkList/LinkList"),s=e("../Map/Map");function a(e,t,r){var l=this;if(void 0===e&&(e=[]),void 0===t&&(t=a.initSize),r=r||function(e){var t,r,n=0,o="";if("number"==typeof e)n=((n=Math.floor(e))<<5)-n,n&=n;else{o="string"!=typeof e?JSON.stringify(e):e;try{for(var s=i(o),a=s.next();!a.done;a=s.next()){n=(n<<5)-n+a.value.charCodeAt(0),n&=n}}catch(e){t={error:e}}finally{try{a&&!a.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}}return n^=n>>>16},0!=(t&t-1))throw new Error("initBucketNum must be 2 to the power of n");var u=0,c=[],h=Math.max(a.initSize,Math.min(a.maxSize,t));this.size=function(){return u},this.empty=function(){return 0===u},this.clear=function(){u=0,h=t,c=[]},this.forEach=function(e){var t=0;c.forEach(function(r){r.forEach(function(r){e(r,t++)})})};this.setElement=function(e,t){var n,l;if(null===e||void 0===e)throw new Error("to avoid some unnecessary errors, we don't suggest you insert null or undefined here");if(null!==t&&void 0!==t){var f=r(e)&h-1;if(c[f]){var p=c[f].size();if(c[f]instanceof o.default){try{for(var d=i(c[f]),g=d.next();!g.done;g=d.next()){var y=g.value;if(y.key===e)return void(y.value=t)}}catch(e){n={error:e}}finally{try{g&&!g.done&&(l=d.return)&&l.call(d)}finally{if(n)throw n.error}}c[f].pushBack({key:e,value:t}),c[f].size()>=a.treeifyThreshold&&(c[f]=new s.default(c[f]))}else c[f].setElement(e,t);var b=c[f].size();u+=b-p}else++u,c[f]=new o.default([{key:e,value:t}]);u>h*a.sigma&&function(e){if(!(e>=a.maxSize)){h=2*e;var t=[];c.forEach(function(n,i){if(!n.empty()){if(n instanceof o.default&&1===n.size()){var l=n.front(),u=l.key,f=l.value;t[r(u)&h-1]=new o.default([{key:u,value:f}])}else if(n instanceof s.default){var p=new o.default,d=new o.default;n.forEach(function(t){0==(r(t.key)&e)?p.pushBack(t):d.pushBack(t)}),p.size()>a.untreeifyThreshold?t[i]=new s.default(p):p.size()&&(t[i]=p),d.size()>a.untreeifyThreshold?t[i+e]=new s.default(d):d.size()&&(t[i+e]=d)}else{var g=new o.default,y=new o.default;n.forEach(function(t){0==(r(t.key)&e)?g.pushBack(t):y.pushBack(t)}),g.size()&&(t[i]=g),y.size()&&(t[i+e]=y)}c[i].clear()}}),c=t}}.call(this,h)}else this.eraseElementByKey(e)},this.getElementByKey=function(e){var t,n,o=r(e)&h-1;if(c[o]){if(c[o]instanceof s.default)return c[o].getElementByKey(e);try{for(var a=i(c[o]),l=a.next();!l.done;l=a.next()){var u=l.value;if(u.key===e)return u.value}}catch(e){t={error:e}}finally{try{l&&!l.done&&(n=a.return)&&n.call(a)}finally{if(t)throw t.error}}}},this.eraseElementByKey=function(e){var t,n,l=r(e)&h-1;if(c[l]){var f=c[l].size();if(c[l]instanceof s.default)c[l].eraseElementByKey(e),c[l].size()<=a.untreeifyThreshold&&(c[l]=new o.default(c[l]));else{var p=-1;try{for(var d=i(c[l]),g=d.next();!g.done;g=d.next()){if(++p,g.value.key===e){c[l].eraseElementByPos(p);break}}}catch(e){t={error:e}}finally{try{g&&!g.done&&(n=d.return)&&n.call(d)}finally{if(t)throw t.error}}}var y=c[l].size();u+=y-f}},this.find=function(e){var t,n,o=r(e)&h-1;if(!c[o])return!1;if(c[o]instanceof s.default)return c[o].find(e);try{for(var a=i(c[o]),l=a.next();!l.done;l=a.next()){if(l.value.key===e)return!0}}catch(e){t={error:e}}finally{try{l&&!l.done&&(n=a.return)&&n.call(a)}finally{if(t)throw t.error}}return!1},this[Symbol.iterator]=function(){return function(){var e,t,r,o,s,a;return n(this,function(n){switch(n.label){case 0:e=0,n.label=1;case 1:if(!(e=h)return[3,10];n.label=2;case 2:n.trys.push([2,7,8,9]),s=void 0,t=i(c[e]),r=t.next(),n.label=3;case 3:return r.done?[3,6]:[4,r.value];case 4:n.sent(),n.label=5;case 5:return r=t.next(),[3,3];case 6:return[3,9];case 7:return o=n.sent(),s={error:o},[3,9];case 8:try{r&&!r.done&&(a=t.return)&&a.call(t)}finally{if(s)throw s.error}return[7];case 9:return++e,[3,1];case 10:return[2]}})}()},e.forEach(function(e){var t=e.key,r=e.value;return l.setElement(t,r)}),Object.freeze(this)}a.initSize=16,a.maxSize=1<<30,a.sigma=.75,a.treeifyThreshold=8,a.untreeifyThreshold=6,a.minTreeifySize=64,Object.freeze(a),r.default=a},{"../LinkList/LinkList":29,"../Map/Map":30}],28:[function(e,t,r){"use strict";var n=this&&this.__generator||function(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(r,"__esModule",{value:!0});var o=e("../Set/Set"),s=e("../LinkList/LinkList");function a(e,t,r){var l=this;if(void 0===e&&(e=[]),void 0===t&&(t=a.initSize),r=r||function(e){var t=0,r="";if("number"==typeof e)t=((t=Math.floor(e))<<5)-t,t&=t;else{r="string"!=typeof e?JSON.stringify(e):e;for(var n=0;n>>16},0!=(t&t-1))throw new Error("initBucketNum must be 2 to the power of n");var u=0,c=[],h=Math.max(a.initSize,Math.min(a.maxSize,t));this.size=function(){return u},this.empty=function(){return 0===u},this.clear=function(){u=0,h=t,c=[]},this.forEach=function(e){var t=0;c.forEach(function(r){r.forEach(function(r){e(r,t++)})})};this.insert=function(e){if(null===e||void 0===e)throw new Error("to avoid some unnecessary errors, we don't suggest you insert null or undefined here");var t=r(e)&h-1;if(c[t]){var n=c[t].size();if(c[t]instanceof s.default){if(c[t].find(e))return;c[t].pushBack(e),c[t].size()>=a.treeifyThreshold&&(c[t]=new o.default(c[t]))}else c[t].insert(e);var i=c[t].size();u+=i-n}else c[t]=new s.default([e]),++u;u>h*a.sigma&&function(e){if(!(e>=a.maxSize)){h=2*e;var t=[];c.forEach(function(n,i){if(!n.empty()){if(n instanceof s.default&&1===n.size()){var l=n.front();if(void 0===l)throw new Error("unknown error");t[r(l)&h-1]=new s.default([l])}else if(n instanceof o.default){var u=new s.default,f=new s.default;n.forEach(function(t){0==(r(t)&e)?u.pushBack(t):f.pushBack(t)}),u.size()>a.untreeifyThreshold?t[i]=new o.default(u):u.size()&&(t[i]=u),f.size()>a.untreeifyThreshold?t[i+e]=new o.default(f):f.size()&&(t[i+e]=f)}else{var p=new s.default,d=new s.default;n.forEach(function(t){0==(r(t)&e)?p.pushBack(t):d.pushBack(t)}),p.size()&&(t[i]=p),d.size()&&(t[i+e]=d)}c[i].clear()}}),c=t}}.call(this,h)},this.eraseElementByValue=function(e){var t=r(e)&h-1;if(c[t]){var n=c[t].size();c[t].eraseElementByValue(e),c[t]instanceof o.default&&c[t].size()<=a.untreeifyThreshold&&(c[t]=new s.default(c[t]));var i=c[t].size();u+=i-n}},this.find=function(e){var t=r(e)&h-1;return!!c[t]&&c[t].find(e)},this[Symbol.iterator]=function(){return function(){var e,t,r,o,s,a;return n(this,function(n){switch(n.label){case 0:e=0,n.label=1;case 1:if(!(e=h)return[3,10];n.label=2;case 2:n.trys.push([2,7,8,9]),s=void 0,t=i(c[e]),r=t.next(),n.label=3;case 3:return r.done?[3,6]:[4,r.value];case 4:n.sent(),n.label=5;case 5:return r=t.next(),[3,3];case 6:return[3,9];case 7:return o=n.sent(),s={error:o},[3,9];case 8:try{r&&!r.done&&(a=t.return)&&a.call(t)}finally{if(s)throw s.error}return[7];case 9:return++e,[3,1];case 10:return[2]}})}()},e.forEach(function(e){return l.insert(e)}),Object.freeze(this)}a.initSize=16,a.maxSize=1<<30,a.sigma=.75,a.treeifyThreshold=8,a.untreeifyThreshold=6,a.minTreeifySize=64,Object.freeze(a),r.default=a},{"../LinkList/LinkList":29,"../Set/Set":33}],29:[function(e,t,r){"use strict";var n=this&&this.__generator||function(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=r)throw new Error("pos must more then 0 and less then the list length");for(var t=o;e--&&t;)t=t.next;if(!t||void 0===t.value)throw new Error("unknown error");return t.value},this.eraseElementByPos=function(e){if(e<0||e>=r)throw new Error("erase pos must more then 0 and less then the list length");if(0===e)this.popFront();else if(e===r-1)this.popBack();else{for(var t=o;e--;){if(!(null===t||void 0===t?void 0:t.next))throw new Error("unknown error");t=t.next}if(!t||!t.pre||!t.next)throw new Error("unknown error");var n=t.pre,i=t.next;i.pre=n,n.next=i,r>0&&--r}},this.eraseElementByValue=function(e){for(;o&&o.value===e;)this.popFront();for(;s&&s.value===e;)this.popBack();if(o)for(var t=o;t;){if(t.value===e){var n=t.pre,i=t.next;i&&(i.pre=n),n&&(n.next=i),r>0&&--r}t=t.next}},this.pushBack=function(e){if(null===e||void 0===e)throw new Error("you can't push null or undefined here");++r;var t=new i(e);s?(s.next=t,t.pre=s,s=t):o=s=t},this.popBack=function(){s&&(r>0&&--r,s&&(o===s?o=s=void 0:(s=s.pre)&&(s.next=void 0)))},this.setElementByPos=function(e,t){if(null===t||void 0===t)throw new Error("you can't set null or undefined here");if(e<0||e>=r)throw new Error("pos must more then 0 and less then the list length");for(var n=o;e--;){if(!n)throw new Error("unknown error");n=n.next}n&&(n.value=t)},this.insert=function(e,t,n){if(void 0===n&&(n=1),null===t||void 0===t)throw new Error("you can't insert null or undefined here");if(e<0||e>r)throw new Error("insert pos must more then 0 and less then or equal to the list length");if(n<0)throw new Error("insert size must more than 0");if(0===e)for(;n--;)this.pushFront(t);else if(e===r)for(;n--;)this.pushBack(t);else{for(var s=o,a=1;a0&&--r;e.next=t.next,e.next&&(e.next.pre=e),e=e.next}},this.sort=function(e){var t=[];this.forEach(function(e){t.push(e)}),t.sort(e);var r=o;t.forEach(function(e){r&&(r.value=e,r=r.next)})},this.pushFront=function(e){if(null===e||void 0===e)throw new Error("you can't push null or undefined here");++r;var t=new i(e);o?(t.next=o,o.pre=t,o=t):o=s=t},this.popFront=function(){o&&(r>0&&--r,o&&(o===s?o=s=void 0:(o=o.next)&&(o.pre=void 0)))},this.merge=function(e){var t=this,n=o;e.forEach(function(e){for(;n&&void 0!==n.value&&n.value<=e;)n=n.next;if(void 0===n)t.pushBack(e),n=s;else if(n===o)t.pushFront(e),n=o;else{++r;var a=n.pre;a&&(a.next=new i(e),a.next.pre=a,a.next.next=n,n&&(n.pre=a.next))}})},this[Symbol.iterator]=function(){return function(){var e;return n(this,function(t){switch(t.label){case 0:e=o,t.label=1;case 1:if(void 0===e)return[3,3];if(!e.value)throw new Error("unknown error");return[4,e.value];case 2:return t.sent(),e=e.next,[3,1];case 3:return[2]}})}()},e.forEach(function(e){return t.pushBack(e)}),Object.freeze(this)}Object.freeze(o),r.default=o},{}],30:[function(e,t,r){"use strict";var n=this&&this.__generator||function(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(r,"__esModule",{value:!0});var o=e("../Base/TreeNode");function s(e,t){var r=this;void 0===e&&(e=[]),t=t||function(e,t){return et?1:0};var s=0,a=new o.default;a.color=o.default.TreeNodeColorType.black,this.size=function(){return s},this.empty=function(){return 0===s},this.clear=function(){s=0,a.key=a.value=void 0,a.leftChild=a.rightChild=a.brother=void 0};var l=function(e){if(!e||void 0===e.key)throw new Error("unknown error");return e.leftChild?l(e.leftChild):e},u=function(e){if(!e||void 0===e.key)throw new Error("unknown error");return e.rightChild?u(e.rightChild):e};this.front=function(){if(!this.empty()){var e=l(a);if(void 0===e.key||void 0===e.value)throw new Error("unknown error");return{key:e.key,value:e.value}}},this.back=function(){if(!this.empty()){var e=u(a);if(void 0===e.key||void 0===e.value)throw new Error("unknown error");return{key:e.key,value:e.value}}},this.forEach=function(e){var t,r,n=0;try{for(var o=i(this),s=o.next();!s.done;s=o.next()){e(s.value,n++)}}catch(e){t={error:e}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}},this.getElementByPos=function(e){var t,r;if(e<0||e>=this.size())throw new Error("pos must more than 0 and less than set's size");var n=0;try{for(var o=i(this),s=o.next();!s.done;s=o.next()){var a=s.value;if(n===e)return a;++n}}catch(e){t={error:e}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}throw new Error("unknown Error")};var c=function(e,r){if(e&&void 0!==e.key&&void 0!==e.value){var n=t(e.key,r);return 0===n?{key:e.key,value:e.value}:n<0?c(e.rightChild,r):c(e.leftChild,r)||{key:e.key,value:e.value}}};this.lowerBound=function(e){return c(a,e)};var h=function(e,r){if(e&&void 0!==e.key&&void 0!==e.value)return t(e.key,r)<=0?h(e.rightChild,r):h(e.leftChild,r)||{key:e.key,value:e.value}};this.upperBound=function(e){return h(a,e)};var f=function(e,r){if(e&&void 0!==e.key&&void 0!==e.value){var n=t(e.key,r);return 0===n?{key:e.key,value:e.value}:n>0?f(e.leftChild,r):f(e.rightChild,r)||{key:e.key,value:e.value}}};this.reverseLowerBound=function(e){return f(a,e)};var p=function(e,r){if(e&&void 0!==e.key&&void 0!==e.value)return t(e.key,r)>=0?p(e.leftChild,r):p(e.rightChild,r)||{key:e.key,value:e.value}};this.reverseUpperBound=function(e){return p(a,e)};var d=function(e){var t=e.parent;if(!t){if(e===a)return;throw new Error("unknown error")}if(e.color!==o.default.TreeNodeColorType.red){var r=e.brother;if(!r)throw new Error("unknown error");if(e===t.leftChild){if(r.color===o.default.TreeNodeColorType.red){r.color=o.default.TreeNodeColorType.black,t.color=o.default.TreeNodeColorType.red;var n=t.rotateLeft();a===t&&(a=n),d(e)}else if(r.color===o.default.TreeNodeColorType.black)if(r.rightChild&&r.rightChild.color===o.default.TreeNodeColorType.red){r.color=t.color,t.color=o.default.TreeNodeColorType.black,r.rightChild&&(r.rightChild.color=o.default.TreeNodeColorType.black);n=t.rotateLeft();a===t&&(a=n),e.color=o.default.TreeNodeColorType.black}else if(r.rightChild&&r.rightChild.color!==o.default.TreeNodeColorType.black||!r.leftChild||r.leftChild.color!==o.default.TreeNodeColorType.red)r.leftChild&&r.leftChild.color!==o.default.TreeNodeColorType.black||r.rightChild&&r.rightChild.color!==o.default.TreeNodeColorType.black||(r.color=o.default.TreeNodeColorType.red,d(t));else{r.color=o.default.TreeNodeColorType.red,r.leftChild&&(r.leftChild.color=o.default.TreeNodeColorType.black);n=r.rotateRight();a===r&&(a=n),d(e)}}else if(e===t.rightChild)if(r.color===o.default.TreeNodeColorType.red){r.color=o.default.TreeNodeColorType.black,t.color=o.default.TreeNodeColorType.red;n=t.rotateRight();a===t&&(a=n),d(e)}else if(r.color===o.default.TreeNodeColorType.black)if(r.leftChild&&r.leftChild.color===o.default.TreeNodeColorType.red){r.color=t.color,t.color=o.default.TreeNodeColorType.black,r.leftChild&&(r.leftChild.color=o.default.TreeNodeColorType.black);n=t.rotateRight();a===t&&(a=n),e.color=o.default.TreeNodeColorType.black}else if(r.leftChild&&r.leftChild.color!==o.default.TreeNodeColorType.black||!r.rightChild||r.rightChild.color!==o.default.TreeNodeColorType.red)r.leftChild&&r.leftChild.color!==o.default.TreeNodeColorType.black||r.rightChild&&r.rightChild.color!==o.default.TreeNodeColorType.black||(r.color=o.default.TreeNodeColorType.red,d(t));else{r.color=o.default.TreeNodeColorType.red,r.rightChild&&(r.rightChild.color=o.default.TreeNodeColorType.black);n=r.rotateLeft();a===r&&(a=n),d(e)}}else e.color=o.default.TreeNodeColorType.black},g=function(e){for(var t=e;t.leftChild||t.rightChild;){if(t.rightChild){t=l(t.rightChild);var r=e.key;e.key=t.key,t.key=r;var n=e.value;e.value=t.value,t.value=n,e=t}if(t.leftChild){t=u(t.leftChild);r=e.key;e.key=t.key,t.key=r;n=e.value;e.value=t.value,t.value=n,e=t}}d(t),t&&t.remove(),--s,a.color=o.default.TreeNodeColorType.black},y=function(e,t){return!(!e||void 0===e.key)&&(!!y(e.leftChild,t)||(!!t(e)||y(e.rightChild,t)))};this.eraseElementByPos=function(e){if(e<0||e>=s)throw new Error("pos must more than 0 and less than set's size");var t=0;y(a,function(r){return e===t?(g(r),!0):(++t,!1)})},this.eraseElementByKey=function(e){if(!this.empty()){var r=v(a,e);void 0!==r&&void 0!==r.key&&0===t(r.key,e)&&g(r)}};var b=function(e,r){if(!e||void 0===e.key)throw new Error("unknown error");var n=t(r,e.key);return n<0?e.leftChild?b(e.leftChild,r):(e.leftChild=new o.default,e.leftChild.parent=e,e.leftChild.brother=e.rightChild,e.rightChild&&(e.rightChild.brother=e.leftChild),e.leftChild):n>0?e.rightChild?b(e.rightChild,r):(e.rightChild=new o.default,e.rightChild.parent=e,e.rightChild.brother=e.leftChild,e.leftChild&&(e.leftChild.brother=e.rightChild),e.rightChild):e},m=function(e){var t=e.parent;if(!t){if(e===a)return;throw new Error("unknown error")}if(t.color!==o.default.TreeNodeColorType.black&&t.color===o.default.TreeNodeColorType.red){var r=t.brother,n=t.parent;if(!n)throw new Error("unknown error");if(r&&r.color===o.default.TreeNodeColorType.red)r.color=t.color=o.default.TreeNodeColorType.black,n.color=o.default.TreeNodeColorType.red,m(n);else if(!r||r.color===o.default.TreeNodeColorType.black)if(t===n.leftChild){if(e===t.leftChild){t.color=o.default.TreeNodeColorType.black,n.color=o.default.TreeNodeColorType.red;var i=n.rotateRight();n===a&&(a=i)}else if(e===t.rightChild){i=t.rotateLeft();n===a&&(a=i),m(t)}}else if(t===n.rightChild)if(e===t.leftChild){i=t.rotateRight();n===a&&(a=i),m(t)}else if(e===t.rightChild){t.color=o.default.TreeNodeColorType.black,n.color=o.default.TreeNodeColorType.red;i=n.rotateLeft();n===a&&(a=i)}}};this.setElement=function(e,r){if(null===e||void 0===e)throw new Error("to avoid some unnecessary errors, we don't suggest you insert null or undefined here");if(null!==r&&void 0!==r){if(this.empty())return++s,a.key=e,a.value=r,void(a.color=o.default.TreeNodeColorType.black);var n=b(a,e);void 0===n.key||0!==t(n.key,e)?(++s,n.key=e,n.value=r,m(n),a.color=o.default.TreeNodeColorType.black):n.value=r}else this.eraseElementByKey(e)};var v=function(e,r){if(e&&void 0!==e.key){var n=t(r,e.key);return n<0?v(e.leftChild,r):n>0?v(e.rightChild,r):e}};this.find=function(e){return!!v(a,e)},this.getElementByKey=function(e){var t=v(a,e);if(void 0===(null===t||void 0===t?void 0:t.key)||void 0===(null===t||void 0===t?void 0:t.value))throw new Error("unknown error");return t.value},this.union=function(e){var t=this;e.forEach(function(e){var r=e.key,n=e.value;return t.setElement(r,n)})},this.getHeight=function(){if(this.empty())return 0;var e=function(t){return t?Math.max(e(t.leftChild),e(t.rightChild))+1:1};return e(a)};var w=function(e){return n(this,function(t){switch(t.label){case 0:return e&&void 0!==e.key&&void 0!==e.value?[5,i(w(e.leftChild))]:[2];case 1:return t.sent(),[4,{key:e.key,value:e.value}];case 2:return t.sent(),[5,i(w(e.rightChild))];case 3:return t.sent(),[2]}})};this[Symbol.iterator]=function(){return w(a)},e.forEach(function(e){var t=e.key,n=e.value;return r.setElement(t,n)}),Object.freeze(this)}Object.freeze(s),r.default=s},{"../Base/TreeNode":25}],31:[function(e,t,r){"use strict";function n(e,t){void 0===e&&(e=[]),t=t||function(e,t){return e>t?-1:e=n)throw new Error("unknown error");if(t<0||t>=n)throw new Error("unknown error");var i=r[e];r[e]=r[t],r[t]=i},o=function(e){if(e<0||e>=n)throw new Error("unknown error");var o=2*e+1,s=2*e+2;o0&&i(e,o),s0&&i(e,s)};!function(){for(var e=Math.floor((n-1)/2);e>=0;--e)for(var o=e,s=2*o+1;s0&&(l=a),t(r[o],r[l])<=0)break;i(o,l),s=2*(o=l)+1}}(),this.size=function(){return n},this.empty=function(){return 0===n},this.clear=function(){n=0,r.length=0},this.push=function(e){if(r.push(e),1!==++n)for(var i=n-1;i>0;){var s=Math.floor((i-1)/2);if(t(r[s],e)<=0)break;o(s),i=s}},this.pop=function(){if(!this.empty())if(1!==this.size()){var e=r[n-1];--n;for(var i=0;i=this.size())break;var a=o;if(s0&&(a=s),t(r[a],e)>=0)break;r[i]=r[a],i=a}r[i]=e}else--n},this.top=function(){return r[0]},Object.freeze(this)}Object.defineProperty(r,"__esModule",{value:!0}),Object.freeze(n),r.default=n},{}],32:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n=e("../LinkList/LinkList");function i(e){void 0===e&&(e=[]);var t=new n.default(e);this.size=function(){return t.size()},this.empty=function(){return t.empty()},this.clear=function(){t.clear()},this.push=function(e){t.pushBack(e)},this.pop=function(){t.popFront()},this.front=function(){return t.front()},Object.freeze(this)}Object.freeze(i),r.default=i},{"../LinkList/LinkList":29}],33:[function(e,t,r){"use strict";var n=this&&this.__generator||function(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(r,"__esModule",{value:!0});var o=e("../Base/TreeNode");function s(e,t){var r=this;void 0===e&&(e=[]),t=t||function(e,t){return et?1:0};var s=0,a=new o.default;a.color=o.default.TreeNodeColorType.black,this.size=function(){return s},this.empty=function(){return 0===s},this.clear=function(){s=0,a.key=void 0,a.leftChild=a.rightChild=a.brother=a.parent=void 0,a.color=o.default.TreeNodeColorType.black};var l=function(e){if(!e||void 0===e.key)throw new Error("unknown error");return e.leftChild?l(e.leftChild):e},u=function(e){if(!e||void 0===e.key)throw new Error("unknown error");return e.rightChild?u(e.rightChild):e};this.front=function(){if(!this.empty())return l(a).key},this.back=function(){if(!this.empty())return u(a).key},this.forEach=function(e){var t,r,n=0;try{for(var o=i(this),s=o.next();!s.done;s=o.next()){e(s.value,n++)}}catch(e){t={error:e}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}},this.getElementByPos=function(e){var t,r;if(e<0||e>=this.size())throw new Error("pos must more than 0 and less than set's size");var n=0;try{for(var o=i(this),s=o.next();!s.done;s=o.next()){var a=s.value;if(n===e)return a;++n}}catch(e){t={error:e}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}throw new Error("unknown error")};var c=function(e){var t=e.parent;if(!t){if(e===a)return;throw new Error("unknown error")}if(e.color!==o.default.TreeNodeColorType.red){var r=e.brother;if(!r)throw new Error("unknown error");if(e===t.leftChild){if(r.color===o.default.TreeNodeColorType.red){r.color=o.default.TreeNodeColorType.black,t.color=o.default.TreeNodeColorType.red;var n=t.rotateLeft();a===t&&(a=n),c(e)}else if(r.color===o.default.TreeNodeColorType.black)if(r.rightChild&&r.rightChild.color===o.default.TreeNodeColorType.red){r.color=t.color,t.color=o.default.TreeNodeColorType.black,r.rightChild&&(r.rightChild.color=o.default.TreeNodeColorType.black);n=t.rotateLeft();a===t&&(a=n),e.color=o.default.TreeNodeColorType.black}else if(r.rightChild&&r.rightChild.color!==o.default.TreeNodeColorType.black||!r.leftChild||r.leftChild.color!==o.default.TreeNodeColorType.red)r.leftChild&&r.leftChild.color!==o.default.TreeNodeColorType.black||r.rightChild&&r.rightChild.color!==o.default.TreeNodeColorType.black||(r.color=o.default.TreeNodeColorType.red,c(t));else{r.color=o.default.TreeNodeColorType.red,r.leftChild&&(r.leftChild.color=o.default.TreeNodeColorType.black);n=r.rotateRight();a===r&&(a=n),c(e)}}else if(e===t.rightChild)if(r.color===o.default.TreeNodeColorType.red){r.color=o.default.TreeNodeColorType.black,t.color=o.default.TreeNodeColorType.red;n=t.rotateRight();a===t&&(a=n),c(e)}else if(r.color===o.default.TreeNodeColorType.black)if(r.leftChild&&r.leftChild.color===o.default.TreeNodeColorType.red){r.color=t.color,t.color=o.default.TreeNodeColorType.black,r.leftChild&&(r.leftChild.color=o.default.TreeNodeColorType.black);n=t.rotateRight();a===t&&(a=n),e.color=o.default.TreeNodeColorType.black}else if(r.leftChild&&r.leftChild.color!==o.default.TreeNodeColorType.black||!r.rightChild||r.rightChild.color!==o.default.TreeNodeColorType.red)r.leftChild&&r.leftChild.color!==o.default.TreeNodeColorType.black||r.rightChild&&r.rightChild.color!==o.default.TreeNodeColorType.black||(r.color=o.default.TreeNodeColorType.red,c(t));else{r.color=o.default.TreeNodeColorType.red,r.rightChild&&(r.rightChild.color=o.default.TreeNodeColorType.black);n=r.rotateLeft();a===r&&(a=n),c(e)}}else e.color=o.default.TreeNodeColorType.black},h=function(e){for(var t=e;t.leftChild||t.rightChild;){if(t.rightChild){t=l(t.rightChild);var r=e.key;e.key=t.key,t.key=r,e=t}if(t.leftChild){t=u(t.leftChild);r=e.key;e.key=t.key,t.key=r,e=t}}c(t),t&&t.remove(),--s,a.color=o.default.TreeNodeColorType.black},f=function(e,t){return!(!e||void 0===e.key)&&(!!f(e.leftChild,t)||(!!t(e)||f(e.rightChild,t)))};this.eraseElementByPos=function(e){if(e<0||e>=s)throw new Error("pos must more than 0 and less than set's size");var t=0;f(a,function(r){return e===t?(h(r),!0):(++t,!1)})},this.eraseElementByValue=function(e){if(!this.empty()){var r=g(a,e);void 0!==r&&void 0!==r.key&&0===t(r.key,e)&&h(r)}};var p=function(e,r){if(!e||void 0===e.key)throw new Error("unknown error");var n=t(r,e.key);return n<0?e.leftChild?p(e.leftChild,r):(e.leftChild=new o.default,e.leftChild.parent=e,e.leftChild.brother=e.rightChild,e.rightChild&&(e.rightChild.brother=e.leftChild),e.leftChild):n>0?e.rightChild?p(e.rightChild,r):(e.rightChild=new o.default,e.rightChild.parent=e,e.rightChild.brother=e.leftChild,e.leftChild&&(e.leftChild.brother=e.rightChild),e.rightChild):e},d=function(e){var t=e.parent;if(!t){if(e===a)return;throw new Error("unknown error")}if(t.color!==o.default.TreeNodeColorType.black&&t.color===o.default.TreeNodeColorType.red){var r=t.brother,n=t.parent;if(!n)throw new Error("unknown error");if(r&&r.color===o.default.TreeNodeColorType.red)r.color=t.color=o.default.TreeNodeColorType.black,n.color=o.default.TreeNodeColorType.red,d(n);else if(!r||r.color===o.default.TreeNodeColorType.black)if(t===n.leftChild){if(e===t.leftChild){t.color=o.default.TreeNodeColorType.black,n.color=o.default.TreeNodeColorType.red;var i=n.rotateRight();n===a&&(a=i)}else if(e===t.rightChild){i=t.rotateLeft();n===a&&(a=i),d(t)}}else if(t===n.rightChild)if(e===t.leftChild){i=t.rotateRight();n===a&&(a=i),d(t)}else if(e===t.rightChild){t.color=o.default.TreeNodeColorType.black,n.color=o.default.TreeNodeColorType.red;i=n.rotateLeft();n===a&&(a=i)}}};this.insert=function(e){if(null===e||void 0===e)throw new Error("to avoid some unnecessary errors, we don't suggest you insert null or undefined here");if(this.empty())return++s,a.key=e,void(a.color=o.default.TreeNodeColorType.black);var r=p(a,e);void 0!==r.key&&0===t(r.key,e)||(++s,r.key=e,d(r),a.color=o.default.TreeNodeColorType.black)};var g=function(e,r){if(e&&void 0!==e.key){var n=t(r,e.key);return n<0?g(e.leftChild,r):n>0?g(e.rightChild,r):e}};this.find=function(e){var r=g(a,e);return void 0!==r&&void 0!==r.key&&0===t(r.key,e)};var y=function(e,r){if(e&&void 0!==e.key){var n=t(e.key,r);if(0===n)return e.key;if(n<0)return y(e.rightChild,r);var i=y(e.leftChild,r);return void 0!==i?i:e.key}};this.lowerBound=function(e){return y(a,e)};var b=function(e,r){if(e&&void 0!==e.key){if(t(e.key,r)<=0)return b(e.rightChild,r);var n=b(e.leftChild,r);return void 0!==n?n:e.key}};this.upperBound=function(e){return b(a,e)};var m=function(e,r){if(e&&void 0!==e.key){var n=t(e.key,r);if(0===n)return e.key;if(n>0)return m(e.leftChild,r);var i=m(e.rightChild,r);return void 0!==i?i:e.key}};this.reverseLowerBound=function(e){return m(a,e)};var v=function(e,r){if(e&&void 0!==e.key){if(t(e.key,r)>=0)return v(e.leftChild,r);var n=v(e.rightChild,r);return void 0!==n?n:e.key}};this.reverseUpperBound=function(e){return v(a,e)},this.union=function(e){var t=this;e.forEach(function(e){return t.insert(e)})},this.getHeight=function(){if(this.empty())return 0;var e=function(t){return t?Math.max(e(t.leftChild),e(t.rightChild))+1:1};return e(a)};var w=function(e){return n(this,function(t){switch(t.label){case 0:return e&&void 0!==e.key?[5,i(w(e.leftChild))]:[2];case 1:return t.sent(),[4,e.key];case 2:return t.sent(),[5,i(w(e.rightChild))];case 3:return t.sent(),[2]}})};this[Symbol.iterator]=function(){return w(a)},e.forEach(function(e){return r.insert(e)}),Object.freeze(this)}Object.freeze(s),r.default=s},{"../Base/TreeNode":25}],34:[function(e,t,r){"use strict";function n(e){var t=this;void 0===e&&(e=[]);var r=0,n=[];this.size=function(){return r},this.empty=function(){return 0===r},this.clear=function(){r=0,n.length=0},this.push=function(e){n.push(e),++r},this.pop=function(){n.pop(),r>0&&--r},this.top=function(){return n[r-1]},e.forEach(function(e){return t.push(e)}),Object.freeze(this)}Object.defineProperty(r,"__esModule",{value:!0}),Object.freeze(n),r.default=n},{}],35:[function(e,t,r){"use strict";var n=this&&this.__generator||function(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function a(o){return function(a){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){i={error:e}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(i)throw i.error}}return s},o=this&&this.__spreadArray||function(e,t,r){if(r||2===arguments.length)for(var n,i=0,o=t.length;i=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")};function a(e){var t=this;void 0===e&&(e=[]);var r=0,a=[];this.size=function(){return r},this.empty=function(){return 0===r},this.clear=function(){r=0,a.length=0},this.front=function(){if(!this.empty())return a[0]},this.back=function(){if(!this.empty())return a[r-1]},this.forEach=function(e){a.forEach(e)},this.getElementByPos=function(e){if(e<0||e>=r)throw new Error("pos must more than 0 and less than vector's size");return a[e]},this.eraseElementByPos=function(e){if(e<0||e>=r)throw new Error("pos must more than 0 and less than vector's size");for(var t=e;tn;)this.popBack()},this.pushBack=function(e){a.push(e),++r},this.popBack=function(){a.pop(),r>0&&--r},this.setElementByPos=function(e,t){if(e<0||e>=r)throw new Error("pos must more than 0 and less than vector's size");a[e]=t},this.insert=function(e,t,n){if(void 0===n&&(n=1),e<0||e>r)throw new Error("pos must more than 0 and less than or equal to vector's size");a.splice.apply(a,o([e,0],i(new Array(n).fill(t)),!1)),r+=n},this.find=function(e){return a.includes(e)},this.reverse=function(){a.reverse()},this.unique=function(){var e,t=[];this.forEach(function(r,n){0!==n&&r===e||(t.push(r),e=r)}),t.forEach(function(e,t){a[t]=e});for(var n=t.length;r>n;)this.popBack()},this.sort=function(e){a.sort(e)},this[Symbol.iterator]=function(){return function(){return n(this,function(e){switch(e.label){case 0:return[5,s(a)];case 1:return[2,e.sent()]}})}()},e.forEach(function(e){return t.pushBack(e)}),Object.freeze(this)}Object.defineProperty(r,"__esModule",{value:!0}),Object.freeze(a),r.default=a},{}],36:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.HashMap=r.HashSet=r.Map=r.Set=r.PriorityQueue=r.Deque=r.LinkList=r.Queue=r.Stack=r.Vector=void 0;var n=e("./Vector/Vector");r.Vector=n.default;var i=e("./Stack/Stack");r.Stack=i.default;var o=e("./Queue/Queue");r.Queue=o.default;var s=e("./LinkList/LinkList");r.LinkList=s.default;var a=e("./Deque/Deque");r.Deque=a.default;var l=e("./PriorityQueue/PriorityQueue");r.PriorityQueue=l.default;var u=e("./Set/Set");r.Set=u.default;var c=e("./Map/Map");r.Map=c.default;var h=e("./HashSet/HashSet");r.HashSet=h.default;var f=e("./HashMap/HashMap");r.HashMap=f.default},{"./Deque/Deque":26,"./HashMap/HashMap":27,"./HashSet/HashSet":28,"./LinkList/LinkList":29,"./Map/Map":30,"./PriorityQueue/PriorityQueue":31,"./Queue/Queue":32,"./Set/Set":33,"./Stack/Stack":34,"./Vector/Vector":35}],37:[function(e,t,r){"use strict";const n=e("yallist"),i=Symbol("max"),o=Symbol("length"),s=Symbol("lengthCalculator"),a=Symbol("allowStale"),l=Symbol("maxAge"),u=Symbol("dispose"),c=Symbol("noDisposeOnSet"),h=Symbol("lruList"),f=Symbol("cache"),p=Symbol("updateAgeOnGet"),d=()=>1;const g=(e,t,r)=>{const n=e[f].get(t);if(n){const t=n.value;if(y(e,t)){if(m(e,n),!e[a])return}else r&&(e[p]&&(n.value.now=Date.now()),e[h].unshiftNode(n));return t.value}},y=(e,t)=>{if(!t||!t.maxAge&&!e[l])return!1;const r=Date.now()-t.now;return t.maxAge?r>t.maxAge:e[l]&&r>e[l]},b=e=>{if(e[o]>e[i])for(let t=e[h].tail;e[o]>e[i]&&null!==t;){const r=t.prev;m(e,t),t=r}},m=(e,t)=>{if(t){const r=t.value;e[u]&&e[u](r.key,r.value),e[o]-=r.length,e[f].delete(r.key),e[h].removeNode(t)}};class v{constructor(e,t,r,n,i){this.key=e,this.value=t,this.length=r,this.now=n,this.maxAge=i||0}}const w=(e,t,r,n)=>{let i=r.value;y(e,i)&&(m(e,r),e[a]||(i=void 0)),i&&t.call(n,i.value,i.key,e)};t.exports=class{constructor(e){if("number"==typeof e&&(e={max:e}),e||(e={}),e.max&&("number"!=typeof e.max||e.max<0))throw new TypeError("max must be a non-negative number");this[i]=e.max||1/0;const t=e.length||d;if(this[s]="function"!=typeof t?d:t,this[a]=e.stale||!1,e.maxAge&&"number"!=typeof e.maxAge)throw new TypeError("maxAge must be a number");this[l]=e.maxAge||0,this[u]=e.dispose,this[c]=e.noDisposeOnSet||!1,this[p]=e.updateAgeOnGet||!1,this.reset()}set max(e){if("number"!=typeof e||e<0)throw new TypeError("max must be a non-negative number");this[i]=e||1/0,b(this)}get max(){return this[i]}set allowStale(e){this[a]=!!e}get allowStale(){return this[a]}set maxAge(e){if("number"!=typeof e)throw new TypeError("maxAge must be a non-negative number");this[l]=e,b(this)}get maxAge(){return this[l]}set lengthCalculator(e){"function"!=typeof e&&(e=d),e!==this[s]&&(this[s]=e,this[o]=0,this[h].forEach(e=>{e.length=this[s](e.value,e.key),this[o]+=e.length})),b(this)}get lengthCalculator(){return this[s]}get length(){return this[o]}get itemCount(){return this[h].length}rforEach(e,t){t=t||this;for(let r=this[h].tail;null!==r;){const n=r.prev;w(this,e,r,t),r=n}}forEach(e,t){t=t||this;for(let r=this[h].head;null!==r;){const n=r.next;w(this,e,r,t),r=n}}keys(){return this[h].toArray().map(e=>e.key)}values(){return this[h].toArray().map(e=>e.value)}reset(){this[u]&&this[h]&&this[h].length&&this[h].forEach(e=>this[u](e.key,e.value)),this[f]=new Map,this[h]=new n,this[o]=0}dump(){return this[h].map(e=>!y(this,e)&&{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[h]}set(e,t,r){if((r=r||this[l])&&"number"!=typeof r)throw new TypeError("maxAge must be a number");const n=r?Date.now():0,a=this[s](t,e);if(this[f].has(e)){if(a>this[i])return m(this,this[f].get(e)),!1;const s=this[f].get(e).value;return this[u]&&(this[c]||this[u](e,s.value)),s.now=n,s.maxAge=r,s.value=t,this[o]+=a-s.length,s.length=a,this.get(e),b(this),!0}const p=new v(e,t,a,n,r);return p.length>this[i]?(this[u]&&this[u](e,t),!1):(this[o]+=p.length,this[h].unshift(p),this[f].set(e,this[h].head),b(this),!0)}has(e){if(!this[f].has(e))return!1;const t=this[f].get(e).value;return!y(this,t)}get(e){return g(this,e,!0)}peek(e){return g(this,e,!1)}pop(){const e=this[h].tail;return e?(m(this,e),e.value):null}del(e){m(this,this[f].get(e))}load(e){this.reset();const t=Date.now();for(let r=e.length-1;r>=0;r--){const n=e[r],i=n.e||0;if(0===i)this.set(n.k,n.v);else{const e=i-t;e>0&&this.set(n.k,n.v,e)}}}prune(){this[f].forEach((e,t)=>g(this,t,!1))}}},{yallist:83}],38:[function(e,t,r){(function(e){(function(){const r=t.exports;r.types={0:"reserved",1:"connect",2:"connack",3:"publish",4:"puback",5:"pubrec",6:"pubrel",7:"pubcomp",8:"subscribe",9:"suback",10:"unsubscribe",11:"unsuback",12:"pingreq",13:"pingresp",14:"disconnect",15:"auth"},r.codes={};for(const e in r.types){const t=r.types[e];r.codes[t]=e}r.CMD_SHIFT=4,r.CMD_MASK=240,r.DUP_MASK=8,r.QOS_MASK=3,r.QOS_SHIFT=1,r.RETAIN_MASK=1,r.VARBYTEINT_MASK=127,r.VARBYTEINT_FIN_MASK=128,r.VARBYTEINT_MAX=268435455,r.SESSIONPRESENT_MASK=1,r.SESSIONPRESENT_HEADER=e.from([r.SESSIONPRESENT_MASK]),r.CONNACK_HEADER=e.from([r.codes.connack<[0,1].map(i=>[0,1].map(o=>{const s=e.alloc(1);return s.writeUInt8(r.codes[t]<e.from([t])),r.EMPTY={pingreq:e.from([r.codes.pingreq<<4,0]),pingresp:e.from([r.codes.pingresp<<4,0]),disconnect:e.from([r.codes.disconnect<<4,0])}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:17}],39:[function(e,t,r){(function(r){(function(){const n=e("./writeToStream"),i=e("events");class o extends i{constructor(){super(),this._array=new Array(20),this._i=0}write(e){return this._array[this._i++]=e,!0}concat(){let e=0;const t=new Array(this._array.length),n=this._array;let i,o=0;for(i=0;i>8,0),r.writeUInt8(255&t,1),r}t.exports={cache:n,generateCache:function(){for(let e=0;e0&&(r|=128),o.writeUInt8(r,n++)}while(t>0&&n<4);return t>0&&(n=0),i?o.subarray(0,n):o.slice(0,n)},generate4ByteBuffer:function(t){const r=e.allocUnsafe(4);return r.writeUInt32BE(t,0),r}}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:17}],42:[function(e,t,r){t.exports=class{constructor(){this.cmd=null,this.retain=!1,this.qos=0,this.dup=!1,this.length=-1,this.topic=null,this.payload=null}}},{}],43:[function(e,t,r){const n=e("bl"),i=e("events"),o=e("./packet"),s=e("./constants"),a=e("debug")("mqtt-packet:parser");class l extends i{constructor(){super(),this.parser=this.constructor.parser}static parser(e){return this instanceof l?(this.settings=e||{},this._states=["_parseHeader","_parseLength","_parsePayload","_newPacket"],this._resetState(),this):(new l).parser(e)}_resetState(){a("_resetState: resetting packet, error, _list, and _stateCounter"),this.packet=new o,this.error=null,this._list=n(),this._stateCounter=0}parse(e){for(this.error&&this._resetState(),this._list.append(e),a("parse: current state: %s",this._states[this._stateCounter]);(-1!==this.packet.length||this._list.length>0)&&this[this._states[this._stateCounter]]()&&!this.error;)this._stateCounter++,a("parse: state complete. _stateCounter is now: %d",this._stateCounter),a("parse: packet.length: %d, buffer list length: %d",this.packet.length,this._list.length),this._stateCounter>=this._states.length&&(this._stateCounter=0);return a("parse: exited while loop. packet: %d, buffer list length: %d",this.packet.length,this._list.length),this._list.length}_parseHeader(){const e=this._list.readUInt8(0);return this.packet.cmd=s.types[e>>s.CMD_SHIFT],this.packet.retain=0!=(e&s.RETAIN_MASK),this.packet.qos=e>>s.QOS_SHIFT&s.QOS_MASK,this.packet.dup=0!=(e&s.DUP_MASK),a("_parseHeader: packet: %o",this.packet),this._list.consume(1),!0}_parseLength(){const e=this._parseVarByteNum(!0);return e&&(this.packet.length=e.value,this._list.consume(e.bytes)),a("_parseLength %d",e.value),!!e}_parsePayload(){a("_parsePayload: payload %O",this._list);let e=!1;if(0===this.packet.length||this._list.length>=this.packet.length){switch(this._pos=0,this.packet.cmd){case"connect":this._parseConnect();break;case"connack":this._parseConnack();break;case"publish":this._parsePublish();break;case"puback":case"pubrec":case"pubrel":case"pubcomp":this._parseConfirmation();break;case"subscribe":this._parseSubscribe();break;case"suback":this._parseSuback();break;case"unsubscribe":this._parseUnsubscribe();break;case"unsuback":this._parseUnsuback();break;case"pingreq":case"pingresp":break;case"disconnect":this._parseDisconnect();break;case"auth":this._parseAuth();break;default:this._emitError(new Error("Not supported"))}e=!0}return a("_parsePayload complete result: %s",e),e}_parseConnect(){let e,t,r,n;a("_parseConnect");const i={},o=this.packet,l=this._parseString();if(null===l)return this._emitError(new Error("Cannot parse protocolId"));if("MQTT"!==l&&"MQIsdp"!==l)return this._emitError(new Error("Invalid protocolId"));if(o.protocolId=l,this._pos>=this._list.length)return this._emitError(new Error("Packet too short"));if(o.protocolVersion=this._list.readUInt8(this._pos),o.protocolVersion>=128&&(o.bridgeMode=!0,o.protocolVersion=o.protocolVersion-128),3!==o.protocolVersion&&4!==o.protocolVersion&&5!==o.protocolVersion)return this._emitError(new Error("Invalid protocol version"));if(this._pos++,this._pos>=this._list.length)return this._emitError(new Error("Packet too short"));if(i.username=this._list.readUInt8(this._pos)&s.USERNAME_MASK,i.password=this._list.readUInt8(this._pos)&s.PASSWORD_MASK,i.will=this._list.readUInt8(this._pos)&s.WILL_FLAG_MASK,i.will&&(o.will={},o.will.retain=0!=(this._list.readUInt8(this._pos)&s.WILL_RETAIN_MASK),o.will.qos=(this._list.readUInt8(this._pos)&s.WILL_QOS_MASK)>>s.WILL_QOS_SHIFT),o.clean=0!=(this._list.readUInt8(this._pos)&s.CLEAN_SESSION_MASK),this._pos++,o.keepalive=this._parseNum(),-1===o.keepalive)return this._emitError(new Error("Packet too short"));if(5===o.protocolVersion){const e=this._parseProperties();Object.getOwnPropertyNames(e).length&&(o.properties=e)}const u=this._parseString();if(null===u)return this._emitError(new Error("Packet too short"));if(o.clientId=u,a("_parseConnect: packet.clientId: %s",o.clientId),i.will){if(5===o.protocolVersion){const e=this._parseProperties();Object.getOwnPropertyNames(e).length&&(o.will.properties=e)}if(null===(e=this._parseString()))return this._emitError(new Error("Cannot parse will topic"));if(o.will.topic=e,a("_parseConnect: packet.will.topic: %s",o.will.topic),null===(t=this._parseBuffer()))return this._emitError(new Error("Cannot parse will payload"));o.will.payload=t,a("_parseConnect: packet.will.paylaod: %s",o.will.payload)}if(i.username){if(null===(n=this._parseString()))return this._emitError(new Error("Cannot parse username"));o.username=n,a("_parseConnect: packet.username: %s",o.username)}if(i.password){if(null===(r=this._parseBuffer()))return this._emitError(new Error("Cannot parse password"));o.password=r}return this.settings=o,a("_parseConnect: complete"),o}_parseConnack(){a("_parseConnack");const e=this.packet;if(this._list.length<1)return null;if(e.sessionPresent=!!(this._list.readUInt8(this._pos++)&s.SESSIONPRESENT_MASK),5===this.settings.protocolVersion)this._list.length>=2?e.reasonCode=this._list.readUInt8(this._pos++):e.reasonCode=0;else{if(this._list.length<2)return null;e.returnCode=this._list.readUInt8(this._pos++)}if(-1===e.returnCode||-1===e.reasonCode)return this._emitError(new Error("Cannot parse return code"));if(5===this.settings.protocolVersion){const t=this._parseProperties();Object.getOwnPropertyNames(t).length&&(e.properties=t)}a("_parseConnack: complete")}_parsePublish(){a("_parsePublish");const e=this.packet;if(e.topic=this._parseString(),null===e.topic)return this._emitError(new Error("Cannot parse topic"));if(!(e.qos>0)||this._parseMessageId()){if(5===this.settings.protocolVersion){const t=this._parseProperties();Object.getOwnPropertyNames(t).length&&(e.properties=t)}e.payload=this._list.slice(this._pos,e.length),a("_parsePublish: payload from buffer list: %o",e.payload)}}_parseSubscribe(){a("_parseSubscribe");const e=this.packet;let t,r,n,i,o,l,u;if(1!==e.qos)return this._emitError(new Error("Wrong subscribe header"));if(e.subscriptions=[],this._parseMessageId()){if(5===this.settings.protocolVersion){const t=this._parseProperties();Object.getOwnPropertyNames(t).length&&(e.properties=t)}for(;this._pos=e.length)return this._emitError(new Error("Malformed Subscribe Payload"));n=(r=this._parseByte())&s.SUBSCRIBE_OPTIONS_QOS_MASK,l=0!=(r>>s.SUBSCRIBE_OPTIONS_NL_SHIFT&s.SUBSCRIBE_OPTIONS_NL_MASK),o=0!=(r>>s.SUBSCRIBE_OPTIONS_RAP_SHIFT&s.SUBSCRIBE_OPTIONS_RAP_MASK),i=r>>s.SUBSCRIBE_OPTIONS_RH_SHIFT&s.SUBSCRIBE_OPTIONS_RH_MASK,u={topic:t,qos:n},5===this.settings.protocolVersion?(u.nl=l,u.rap=o,u.rh=i):this.settings.bridgeMode&&(u.rh=0,u.rap=!0,u.nl=!0),a("_parseSubscribe: push subscription `%s` to subscription",u),e.subscriptions.push(u)}}}_parseSuback(){a("_parseSuback");const e=this.packet;if(this.packet.granted=[],this._parseMessageId()){if(5===this.settings.protocolVersion){const t=this._parseProperties();Object.getOwnPropertyNames(t).length&&(e.properties=t)}for(;this._pos2?(e.reasonCode=this._parseByte(),a("_parseConfirmation: packet.reasonCode `%d`",e.reasonCode)):e.reasonCode=0,e.length>3)){const t=this._parseProperties();Object.getOwnPropertyNames(t).length&&(e.properties=t)}return!0}_parseDisconnect(){const e=this.packet;if(a("_parseDisconnect"),5===this.settings.protocolVersion){this._list.length>0?e.reasonCode=this._parseByte():e.reasonCode=0;const t=this._parseProperties();Object.getOwnPropertyNames(t).length&&(e.properties=t)}return a("_parseDisconnect result: true"),!0}_parseAuth(){a("_parseAuth");const e=this.packet;if(5!==this.settings.protocolVersion)return this._emitError(new Error("Not supported auth packet for this version MQTT"));e.reasonCode=this._parseByte();const t=this._parseProperties();return Object.getOwnPropertyNames(t).length&&(e.properties=t),a("_parseAuth: result: true"),!0}_parseMessageId(){const e=this.packet;return e.messageId=this._parseNum(),null===e.messageId?(this._emitError(new Error("Cannot parse messageId")),!1):(a("_parseMessageId: packet.messageId %d",e.messageId),!0)}_parseString(e){const t=this._parseNum(),r=t+this._pos;if(-1===t||r>this._list.length||r>this.packet.length)return null;const n=this._list.toString("utf8",this._pos,r);return this._pos+=t,a("_parseString: result: %s",n),n}_parseStringPair(){return a("_parseStringPair"),{name:this._parseString(),value:this._parseString()}}_parseBuffer(){const e=this._parseNum(),t=e+this._pos;if(-1===e||t>this._list.length||t>this.packet.length)return null;const r=this._list.slice(this._pos,t);return this._pos+=e,a("_parseBuffer: result: %o",r),r}_parseNum(){if(this._list.length-this._pos<2)return-1;const e=this._list.readUInt16BE(this._pos);return this._pos+=2,a("_parseNum: result: %s",e),e}_parse4ByteNum(){if(this._list.length-this._pos<4)return-1;const e=this._list.readUInt32BE(this._pos);return this._pos+=4,a("_parse4ByteNum: result: %s",e),e}_parseVarByteNum(e){a("_parseVarByteNum");let t,r=0,n=1,i=0,o=!1;const l=this._pos?this._pos:0;for(;r<4&&l+r=r&&this._emitError(new Error("Invalid variable byte integer")),l&&(this._pos+=r),a("_parseVarByteNum: result: %o",o=!!o&&(e?{bytes:r,value:i}:i)),o}_parseByte(){let e;return this._pos=4)&&(h||u))y+=r.byteLength(h)+2;else{if(a<4)return t.emit("error",new Error("clientId must be supplied before 3.1.1")),!1;if(1*u==0)return t.emit("error",new Error("clientId must be given if cleanSession set to 0")),!1}if("number"!=typeof c||c<0||c>65535||c%1!=0)return t.emit("error",new Error("Invalid keepalive")),!1;y+=2;if(y+=1,5===a){var b=C(t,g);if(!b)return!1;y+=b.length}if(l){if("object"!=typeof l)return t.emit("error",new Error("Invalid will")),!1;if(!l.topic||"string"!=typeof l.topic)return t.emit("error",new Error("Invalid will topic")),!1;if(y+=r.byteLength(l.topic)+2,y+=2,l.payload){if(!(l.payload.length>=0))return t.emit("error",new Error("Invalid will payload")),!1;"string"==typeof l.payload?y+=r.byteLength(l.payload):y+=l.payload.length}var m={};if(5===a){if(!(m=C(t,l.properties)))return!1;y+=m.length}}let _=!1;if(null!=f){if(!P(f))return t.emit("error",new Error("Invalid username")),!1;_=!0,y+=r.byteLength(f)+2}if(null!=p){if(!_)return t.emit("error",new Error("Username is required to use password")),!1;if(!P(p))return t.emit("error",new Error("Invalid password")),!1;y+=I(p)+2}t.write(n.CONNECT_HEADER),v(t,y),E(t,s),o.bridgeMode&&(a+=128);t.write(131===a?n.VERSION131:132===a?n.VERSION132:4===a?n.VERSION4:5===a?n.VERSION5:n.VERSION3);let k=0;k|=null!=f?n.USERNAME_MASK:0,k|=null!=p?n.PASSWORD_MASK:0,k|=l&&l.retain?n.WILL_RETAIN_MASK:0,k|=l&&l.qos?l.qos<0&&d(t,p);null!=b&&b.write();return l("publish: payload: %o",f),t.write(f)}(e,t,s);case"puback":case"pubrec":case"pubrel":case"pubcomp":return function(e,t,i){const o=i?i.protocolVersion:4,s=e||{},a=s.cmd||"puback",l=s.messageId,u=s.dup&&"pubrel"===a?n.DUP_MASK:0;let c=0;const h=s.reasonCode,f=s.properties;let p=5===o?3:2;"pubrel"===a&&(c=1);if("number"!=typeof l)return t.emit("error",new Error("Invalid messageId")),!1;let g=null;if(5===o&&"object"==typeof f){if(!(g=T(t,f,i,p)))return!1;p+=g.length}t.write(n.ACKS[a][c][u][0]),v(t,p),d(t,l),5===o&&t.write(r.from([h]));null!==g&&g.write();return!0}(e,t,s);case"subscribe":return function(e,t,i){l("subscribe: packet: ");const o=i?i.protocolVersion:4,s=e||{},a=s.dup?n.DUP_MASK:0,u=s.messageId,c=s.subscriptions,h=s.properties;let f=0;if("number"!=typeof u)return t.emit("error",new Error("Invalid messageId")),!1;f+=2;let p=null;if(5===o){if(!(p=C(t,h)))return!1;f+=p.length}if("object"!=typeof c||!c.length)return t.emit("error",new Error("Invalid subscriptions")),!1;for(let e=0;e2)return t.emit("error",new Error("Invalid subscriptions - invalid Retain Handling")),!1}f+=r.byteLength(n)+2+1}l("subscribe: writing to stream: %o",n.SUBSCRIBE_HEADER),t.write(n.SUBSCRIBE_HEADER[1][a?1:0][0]),v(t,f),d(t,u),null!==p&&p.write();let g=!0;for(const e of c){const i=e.topic,s=e.qos,a=+e.nl,l=+e.rap,u=e.rh;let c;w(t,i),c=n.SUBSCRIBE_OPTIONS_QOS[s],5===o&&(c|=a?n.SUBSCRIBE_OPTIONS_NL:0,c|=l?n.SUBSCRIBE_OPTIONS_RAP:0,c|=u?n.SUBSCRIBE_OPTIONS_RH[u]:0),g=t.write(r.from([c]))}return g}(e,t,s);case"suback":return function(e,t,i){const o=i?i.protocolVersion:4,s=e||{},a=s.messageId,l=s.granted,u=s.properties;let c=0;if("number"!=typeof a)return t.emit("error",new Error("Invalid messageId")),!1;c+=2;if("object"!=typeof l||!l.length)return t.emit("error",new Error("Invalid qos vector")),!1;for(let e=0;ed===k,set(e){e?(u&&0!==Object.keys(u).length||(g=!0),d=k):(g=!1,d=S)}});const m={};function v(e,t){if(t>n.VARBYTEINT_MAX)return e.emit("error",new Error(`Invalid variable byte integer: ${t}`)),!1;let r=m[t];return r||(r=f(t),t<16384&&(m[t]=r)),l("writeVarByteInt: writing to stream: %o",r),e.write(r)}function w(e,t){const n=r.byteLength(t);return d(e,n),l("writeString: %s",t),e.write(t,"utf8")}function _(e,t,r){w(e,t),w(e,r)}function k(e,t){return l("writeNumberCached: number: %d",t),l("writeNumberCached: %o",u[t]),e.write(u[t])}function S(e,t){const r=c(t);return l("writeNumberGenerated: %o",r),e.write(r)}function E(e,t){"string"==typeof t?w(e,t):t?(d(e,t.length),e.write(t)):d(e,0)}function C(e,t){if("object"!=typeof t||null!=t.length)return{length:1,write(){A(e,{},0)}};let i=0;function o(t,i){let o=0;switch(n.propertiesTypes[t]){case"byte":if("boolean"!=typeof i)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=2;break;case"int8":if("number"!=typeof i||i<0||i>255)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=2;break;case"binary":if(i&&null===i)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=1+r.byteLength(i)+2;break;case"int16":if("number"!=typeof i||i<0||i>65535)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=3;break;case"int32":if("number"!=typeof i||i<0||i>4294967295)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=5;break;case"var":if("number"!=typeof i||i<0||i>268435455)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=1+r.byteLength(f(i));break;case"string":if("string"!=typeof i)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=3+r.byteLength(i.toString());break;case"pair":if("object"!=typeof i)return e.emit("error",new Error(`Invalid ${t}: ${i}`)),!1;o+=Object.getOwnPropertyNames(i).reduce((e,t)=>{const n=i[t];return Array.isArray(n)?e+=n.reduce((e,n)=>e+=3+r.byteLength(t.toString())+2+r.byteLength(n.toString()),0):e+=3+r.byteLength(t.toString())+2+r.byteLength(i[t].toString()),e},0);break;default:return e.emit("error",new Error(`Invalid property ${t}: ${i}`)),!1}return o}if(t)for(const e in t){let r=0,n=0;const s=t[e];if(Array.isArray(s))for(let t=0;to;){const r=i.shift();if(!r||!t[r])return!1;delete t[r],s=C(e,t)}return s}function x(e,t,i){switch(n.propertiesTypes[t]){case"byte":e.write(r.from([n.properties[t]])),e.write(r.from([+i]));break;case"int8":e.write(r.from([n.properties[t]])),e.write(r.from([i]));break;case"binary":e.write(r.from([n.properties[t]])),E(e,i);break;case"int16":e.write(r.from([n.properties[t]])),d(e,i);break;case"int32":e.write(r.from([n.properties[t]])),function(e,t){const r=p(t);l("write4ByteNumber: %o",r),e.write(r)}(e,i);break;case"var":e.write(r.from([n.properties[t]])),v(e,i);break;case"string":e.write(r.from([n.properties[t]])),w(e,i);break;case"pair":Object.getOwnPropertyNames(i).forEach(o=>{const s=i[o];Array.isArray(s)?s.forEach(i=>{e.write(r.from([n.properties[t]])),_(e,o.toString(),i.toString())}):(e.write(r.from([n.properties[t]])),_(e,o.toString(),s.toString()))});break;default:return e.emit("error",new Error(`Invalid property ${t} value: ${i}`)),!1}}function A(e,t,r){v(e,r);for(const r in t)if(Object.prototype.hasOwnProperty.call(t,r)&&null!==t[r]){const n=t[r];if(Array.isArray(n))for(let t=0;t=1.5*r;return Math.round(e/r)+" "+n+(i?"s":"")}t.exports=function(e,t){t=t||{};var r=typeof e;if("string"===r&&e.length>0)return function(e){if((e=String(e)).length>100)return;var t=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(!t)return;var r=parseFloat(t[1]);switch((t[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return r*l;case"weeks":case"week":case"w":return r*a;case"days":case"day":case"d":return r*s;case"hours":case"hour":case"hrs":case"hr":case"h":return r*o;case"minutes":case"minute":case"mins":case"min":case"m":return r*i;case"seconds":case"second":case"secs":case"sec":case"s":return r*n;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}(e);if("number"===r&&isFinite(e))return t.long?function(e){var t=Math.abs(e);if(t>=s)return u(e,t,s,"day");if(t>=o)return u(e,t,o,"hour");if(t>=i)return u(e,t,i,"minute");if(t>=n)return u(e,t,n,"second");return e+" ms"}(e):function(e){var t=Math.abs(e);if(t>=s)return Math.round(e/s)+"d";if(t>=o)return Math.round(e/o)+"h";if(t>=i)return Math.round(e/i)+"m";if(t>=n)return Math.round(e/n)+"s";return e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},{}],46:[function(e,t,r){const n=e("./lib/number-allocator.js");t.exports.NumberAllocator=n},{"./lib/number-allocator.js":47}],47:[function(e,t,r){"use strict";const n=e("js-sdsl").Set,i=e("debug")("number-allocator:trace"),o=e("debug")("number-allocator:error");function s(e,t){this.low=e,this.high=t}function a(e,t){if(!(this instanceof a))return new a(e,t);this.min=e,this.max=t,this.ss=new n([],(e,t)=>e.compare(t)),i("Create"),this.clear()}s.prototype.equals=function(e){return this.low===e.low&&this.high===e.high},s.prototype.compare=function(e){return this.lowe)return!1;if(r.low===e)return++r.low,i("use():"+e),!0;if(r.high===e)return--r.high,i("use():"+e),!0;const n=r.low;return r.low=e+1,this.ss.insert(new s(n,e-1)),i("use():"+e),!0}return i("use():failed"),!1},a.prototype.free=function(e){if(ethis.max)return void o("free():"+e+" is out of range");const t=new s(e,e),r=this.ss.lowerBound(t);if(r){if(r.low<=e&&e<=r.high)return void o("free():"+e+" has already been vacant");if(r===this.ss.front())e+1===r.low?--r.low:this.ss.insert(t);else{const n=this.ss.reverseLowerBound(t);n.high+1===e?e+1===r.low?(this.ss.eraseElementByValue(n),r.low=n.low):n.high=e:e+1===r.low?r.low=e:this.ss.insert(t)}}else{if(r===this.ss.front())return void this.ss.insert(t);const n=this.ss.reverseLowerBound(t);n.high+1===e?n.high=e:this.ss.insert(t)}i("free():"+e)},a.prototype.clear=function(){i("clear()"),this.ss.clear(),this.ss.insert(new s(this.min,this.max))},a.prototype.intervalCount=function(){return this.ss.size()},a.prototype.dump=function(){console.log("length:"+this.ss.size());for(const e of this.ss)console.log(e)},t.exports=a},{debug:18,"js-sdsl":36}],48:[function(e,t,r){var n=e("wrappy");function i(e){var t=function(){return t.called?t.value:(t.called=!0,t.value=e.apply(this,arguments))};return t.called=!1,t}function o(e){var t=function(){if(t.called)throw new Error(t.onceError);return t.called=!0,t.value=e.apply(this,arguments)},r=e.name||"Function wrapped with `once`";return t.onceError=r+" shouldn't be called more than once",t.called=!1,t}t.exports=n(i),t.exports.strict=n(o),i.proto=i(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return i(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return o(this)},configurable:!0})})},{wrappy:79}],49:[function(e,t,r){(function(e){(function(){"use strict";void 0===e||!e.version||0===e.version.indexOf("v0.")||0===e.version.indexOf("v1.")&&0!==e.version.indexOf("v1.8.")?t.exports={nextTick:function(t,r,n,i){if("function"!=typeof t)throw new TypeError('"callback" argument must be a function');var o,s,a=arguments.length;switch(a){case 0:case 1:return e.nextTick(t);case 2:return e.nextTick(function(){t.call(null,r)});case 3:return e.nextTick(function(){t.call(null,r,n)});case 4:return e.nextTick(function(){t.call(null,r,n,i)});default:for(o=new Array(a-1),s=0;s1)for(var r=1;r= 0x80 (not a basic code point)","invalid-input":"Invalid input"},k=c-h,S=Math.floor,E=String.fromCharCode;function C(e){throw new RangeError(_[e])}function T(e,t){for(var r=e.length,n=[];r--;)n[r]=t(e[r]);return n}function x(e,t){var r=e.split("@"),n="";return r.length>1&&(n=r[0]+"@",e=r[1]),n+T((e=e.replace(w,".")).split("."),t).join(".")}function A(e){for(var t,r,n=[],i=0,o=e.length;i=55296&&t<=56319&&i65535&&(t+=E((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=E(e)}).join("")}function P(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function O(e,t,r){var n=0;for(e=r?S(e/d):e>>1,e+=S(e/t);e>k*f>>1;n+=c)e=S(e/k);return S(n+(k+1)*e/(e+p))}function B(e){var t,r,n,i,o,s,a,l,p,d,m,v=[],w=e.length,_=0,k=y,E=g;for((r=e.lastIndexOf(b))<0&&(r=0),n=0;n=128&&C("not-basic"),v.push(e.charCodeAt(n));for(i=r>0?r+1:0;i=w&&C("invalid-input"),((l=(m=e.charCodeAt(i++))-48<10?m-22:m-65<26?m-65:m-97<26?m-97:c)>=c||l>S((u-_)/s))&&C("overflow"),_+=l*s,!(l<(p=a<=E?h:a>=E+f?f:a-E));a+=c)s>S(u/(d=c-p))&&C("overflow"),s*=d;E=O(_-o,t=v.length+1,0==o),S(_/t)>u-k&&C("overflow"),k+=S(_/t),_%=t,v.splice(_++,0,k)}return I(v)}function R(e){var t,r,n,i,o,s,a,l,p,d,m,v,w,_,k,T=[];for(v=(e=A(e)).length,t=y,r=0,o=g,s=0;s=t&&mS((u-r)/(w=n+1))&&C("overflow"),r+=(a-t)*w,t=a,s=0;su&&C("overflow"),m==t){for(l=r,p=c;!(l<(d=p<=o?h:p>=o+f?f:p-o));p+=c)k=l-d,_=c-d,T.push(E(P(d+k%_,0))),l=S(k/_);T.push(E(P(l,0))),o=O(r,w,n==i),r=0,++n}++r,++t}return T.join("")}if(a={version:"1.4.1",ucs2:{decode:A,encode:I},decode:B,encode:R,toASCII:function(e){return x(e,function(e){return v.test(e)?"xn--"+R(e):e})},toUnicode:function(e){return x(e,function(e){return m.test(e)?B(e.slice(4).toLowerCase()):e})}},i&&o)if(t.exports==i)o.exports=a;else for(l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);else n.punycode=a}(this)}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],52:[function(e,t,r){"use strict";function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.exports=function(e,t,r,o){t=t||"&",r=r||"=";var s={};if("string"!=typeof e||0===e.length)return s;var a=/\+/g;e=e.split(t);var l=1e3;o&&"number"==typeof o.maxKeys&&(l=o.maxKeys);var u=e.length;l>0&&u>l&&(u=l);for(var c=0;c=0?(h=g.substr(0,y),f=g.substr(y+1)):(h=g,f=""),p=decodeURIComponent(h),d=decodeURIComponent(f),n(s,p)?i(s[p])?s[p].push(d):s[p]=[s[p],d]:s[p]=d}return s};var i=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},{}],53:[function(e,t,r){"use strict";var n=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};t.exports=function(e,t,r,a){return t=t||"&",r=r||"=",null===e&&(e=void 0),"object"==typeof e?o(s(e),function(s){var a=encodeURIComponent(n(s))+r;return i(e[s])?o(e[s],function(e){return a+encodeURIComponent(n(e))}).join(t):a+encodeURIComponent(n(e[s]))}).join(t):a?encodeURIComponent(n(a))+r+encodeURIComponent(n(e)):""};var i=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};function o(e,t){if(e.map)return e.map(t);for(var r=[],n=0;n2?"one of ".concat(t," ").concat(e.slice(0,r-1).join(", "),", or ")+e[r-1]:2===r?"one of ".concat(t," ").concat(e[0]," or ").concat(e[1]):"of ".concat(t," ").concat(e[0])}return"of ".concat(t," ").concat(String(e))}i("ERR_INVALID_OPT_VALUE",function(e,t){return'The value "'+t+'" is invalid for option "'+e+'"'},TypeError),i("ERR_INVALID_ARG_TYPE",function(e,t,r){var n,i,s,a;if("string"==typeof t&&(i="not ",t.substr(!s||s<0?0:+s,i.length)===i)?(n="must not be",t=t.replace(/^not /,"")):n="must be",function(e,t,r){return(void 0===r||r>e.length)&&(r=e.length),e.substring(r-t.length,r)===t}(e," argument"))a="The ".concat(e," ").concat(n," ").concat(o(t,"type"));else{var l=function(e,t,r){return"number"!=typeof r&&(r=0),!(r+t.length>e.length)&&-1!==e.indexOf(t,r)}(e,".")?"property":"argument";a='The "'.concat(e,'" ').concat(l," ").concat(n," ").concat(o(t,"type"))}return a+=". Received type ".concat(typeof r)},TypeError),i("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF"),i("ERR_METHOD_NOT_IMPLEMENTED",function(e){return"The "+e+" method is not implemented"}),i("ERR_STREAM_PREMATURE_CLOSE","Premature close"),i("ERR_STREAM_DESTROYED",function(e){return"Cannot call "+e+" after a stream was destroyed"}),i("ERR_MULTIPLE_CALLBACK","Callback called multiple times"),i("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable"),i("ERR_STREAM_WRITE_AFTER_END","write after end"),i("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError),i("ERR_UNKNOWN_ENCODING",function(e){return"Unknown encoding: "+e},TypeError),i("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event"),t.exports.codes=n},{}],56:[function(e,t,r){(function(r){(function(){"use strict";var n=Object.keys||function(e){var t=[];for(var r in e)t.push(r);return t};t.exports=u;var i=e("./_stream_readable"),o=e("./_stream_writable");e("inherits")(u,i);for(var s=n(o.prototype),a=0;a0)if("string"==typeof t||s.objectMode||Object.getPrototypeOf(t)===a.prototype||(t=function(e){return a.from(e)}(t)),n)s.endEmitted?k(e,new _):x(e,s,t,!0);else if(s.ended)k(e,new v);else{if(s.destroyed)return!1;s.reading=!1,s.decoder&&!r?(t=s.decoder.write(t),s.objectMode||0!==t.length?x(e,s,t,!1):B(e,s)):x(e,s,t,!1)}else n||(s.reading=!1,B(e,s));return!s.ended&&(s.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=A?e=A:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function P(e){var t=e._readableState;u("emitReadable",t.needReadable,t.emittedReadable),t.needReadable=!1,t.emittedReadable||(u("emitReadable",t.flowing),t.emittedReadable=!0,r.nextTick(O,e))}function O(e){var t=e._readableState;u("emitReadable_",t.destroyed,t.length,t.ended),t.destroyed||!t.length&&!t.ended||(e.emit("readable"),t.emittedReadable=!1),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,j(e)}function B(e,t){t.readingMore||(t.readingMore=!0,r.nextTick(R,e,t))}function R(e,t){for(;!t.reading&&!t.ended&&(t.length0,t.resumeScheduled&&!t.paused?t.flowing=!0:e.listenerCount("data")>0&&e.resume()}function N(e){u("readable nexttick read 0"),e.read(0)}function L(e,t){u("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),j(e),t.flowing&&!t.reading&&e.read(0)}function j(e){var t=e._readableState;for(u("flow",t.flowing);t.flowing&&null!==e.read(););}function U(e,t){return 0===t.length?null:(t.objectMode?r=t.buffer.shift():!e||e>=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.first():t.buffer.concat(t.length),t.buffer.clear()):r=t.buffer.consume(e,t.decoder),r);var r}function q(e){var t=e._readableState;u("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,r.nextTick(D,t,e))}function D(e,t){if(u("endReadableNT",e.endEmitted,e.length),!e.endEmitted&&0===e.length&&(e.endEmitted=!0,t.readable=!1,t.emit("end"),e.autoDestroy)){var r=t._writableState;(!r||r.autoDestroy&&r.finished)&&t.destroy()}}function z(e,t){for(var r=0,n=e.length;r=t.highWaterMark:t.length>0)||t.ended))return u("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?q(this):P(this),null;if(0===(e=I(e,t))&&t.ended)return 0===t.length&&q(this),null;var n,i=t.needReadable;return u("need readable",i),(0===t.length||t.length-e0?U(e,t):null)?(t.needReadable=t.length<=t.highWaterMark,e=0):(t.length-=e,t.awaitDrain=0),0===t.length&&(t.ended||(t.needReadable=!0),r!==e&&t.ended&&q(this)),null!==n&&this.emit("data",n),n},C.prototype._read=function(e){k(this,new w("_read()"))},C.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,u("pipe count=%d opts=%j",i.pipesCount,t);var s=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?l:y;function a(t,r){u("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,u("cleanup"),e.removeListener("close",d),e.removeListener("finish",g),e.removeListener("drain",c),e.removeListener("error",p),e.removeListener("unpipe",a),n.removeListener("end",l),n.removeListener("end",y),n.removeListener("data",f),h=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||c())}function l(){u("onend"),e.end()}i.endEmitted?r.nextTick(s):n.once("end",s),e.on("unpipe",a);var c=function(e){return function(){var t=e._readableState;u("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&o(e,"data")&&(t.flowing=!0,j(e))}}(n);e.on("drain",c);var h=!1;function f(t){u("ondata");var r=e.write(t);u("dest.write",r),!1===r&&((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==z(i.pipes,e))&&!h&&(u("false write response, pause",i.awaitDrain),i.awaitDrain++),n.pause())}function p(t){u("onerror",t),y(),e.removeListener("error",p),0===o(e,"error")&&k(e,t)}function d(){e.removeListener("finish",g),y()}function g(){u("onfinish"),e.removeListener("close",d),y()}function y(){u("unpipe"),n.unpipe(e)}return n.on("data",f),function(e,t,r){if("function"==typeof e.prependListener)return e.prependListener(t,r);e._events&&e._events[t]?Array.isArray(e._events[t])?e._events[t].unshift(r):e._events[t]=[r,e._events[t]]:e.on(t,r)}(e,"error",p),e.once("close",d),e.once("finish",g),e.emit("pipe",n),i.flowing||(u("pipe resume"),n.resume()),e},C.prototype.unpipe=function(e){var t=this._readableState,r={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,r),this);if(!e){var n=t.pipes,i=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var o=0;o0,!1!==i.flowing&&this.resume()):"readable"===e&&(i.endEmitted||i.readableListening||(i.readableListening=i.needReadable=!0,i.flowing=!1,i.emittedReadable=!1,u("on readable",i.length,i.reading),i.length?P(this):i.reading||r.nextTick(N,this))),n},C.prototype.addListener=C.prototype.on,C.prototype.removeListener=function(e,t){var n=s.prototype.removeListener.call(this,e,t);return"readable"===e&&r.nextTick(M,this),n},C.prototype.removeAllListeners=function(e){var t=s.prototype.removeAllListeners.apply(this,arguments);return"readable"!==e&&void 0!==e||r.nextTick(M,this),t},C.prototype.resume=function(){var e=this._readableState;return e.flowing||(u("resume"),e.flowing=!e.readableListening,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,r.nextTick(L,e,t))}(this,e)),e.paused=!1,this},C.prototype.pause=function(){return u("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(u("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this},C.prototype.wrap=function(e){var t=this,r=this._readableState,n=!1;for(var i in e.on("end",function(){if(u("wrapped end"),r.decoder&&!r.ended){var e=r.decoder.end();e&&e.length&&t.push(e)}t.push(null)}),e.on("data",function(i){(u("wrapped data"),r.decoder&&(i=r.decoder.write(i)),!r.objectMode||null!==i&&void 0!==i)&&((r.objectMode||i&&i.length)&&(t.push(i)||(n=!0,e.pause())))}),e)void 0===this[i]&&"function"==typeof e[i]&&(this[i]=function(t){return function(){return e[t].apply(e,arguments)}}(i));for(var o=0;o-1))throw new _(e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(C.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(C.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),C.prototype._write=function(e,t,r){r(new g("_write()"))},C.prototype._writev=null,C.prototype.end=function(e,t,n){var i=this._writableState;return"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!==e&&void 0!==e&&this.write(e,t),i.corked&&(i.corked=1,this.uncork()),i.ending||function(e,t,n){t.ending=!0,O(e,t),n&&(t.finished?r.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,i,n),this},Object.defineProperty(C.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(C.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),C.prototype.destroy=h.destroy,C.prototype._undestroy=h.undestroy,C.prototype._destroy=function(e,t){t(e)}}).call(this)}).call(this,e("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../errors":55,"./_stream_duplex":56,"./internal/streams/destroy":63,"./internal/streams/state":67,"./internal/streams/stream":68,_process:50,buffer:17,inherits:24,"util-deprecate":78}],61:[function(e,t,r){(function(r){(function(){"use strict";var n;function i(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var o=e("./end-of-stream"),s=Symbol("lastResolve"),a=Symbol("lastReject"),l=Symbol("error"),u=Symbol("ended"),c=Symbol("lastPromise"),h=Symbol("handlePromise"),f=Symbol("stream");function p(e,t){return{value:e,done:t}}function d(e){var t=e[s];if(null!==t){var r=e[f].read();null!==r&&(e[c]=null,e[s]=null,e[a]=null,t(p(r,!1)))}}var g=Object.getPrototypeOf(function(){}),y=Object.setPrototypeOf((i(n={get stream(){return this[f]},next:function(){var e=this,t=this[l];if(null!==t)return Promise.reject(t);if(this[u])return Promise.resolve(p(void 0,!0));if(this[f].destroyed)return new Promise(function(t,n){r.nextTick(function(){e[l]?n(e[l]):t(p(void 0,!0))})});var n,i=this[c];if(i)n=new Promise(function(e,t){return function(r,n){e.then(function(){t[u]?r(p(void 0,!0)):t[h](r,n)},n)}}(i,this));else{var o=this[f].read();if(null!==o)return Promise.resolve(p(o,!1));n=new Promise(this[h])}return this[c]=n,n}},Symbol.asyncIterator,function(){return this}),i(n,"return",function(){var e=this;return new Promise(function(t,r){e[f].destroy(null,function(e){e?r(e):t(p(void 0,!0))})})}),n),g);t.exports=function(e){var t,n=Object.create(y,(i(t={},f,{value:e,writable:!0}),i(t,s,{value:null,writable:!0}),i(t,a,{value:null,writable:!0}),i(t,l,{value:null,writable:!0}),i(t,u,{value:e._readableState.endEmitted,writable:!0}),i(t,h,{value:function(e,t){var r=n[f].read();r?(n[c]=null,n[s]=null,n[a]=null,e(p(r,!1))):(n[s]=e,n[a]=t)},writable:!0}),t));return n[c]=null,o(e,function(e){if(e&&"ERR_STREAM_PREMATURE_CLOSE"!==e.code){var t=n[a];return null!==t&&(n[c]=null,n[s]=null,n[a]=null,t(e)),void(n[l]=e)}var r=n[s];null!==r&&(n[c]=null,n[s]=null,n[a]=null,r(p(void 0,!0))),n[u]=!0}),e.on("readable",function(e){r.nextTick(d,e)}.bind(null,n)),n}}).call(this)}).call(this,e("_process"))},{"./end-of-stream":64,_process:50}],62:[function(e,t,r){"use strict";function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function i(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){for(var r=0;r0?this.tail.next=t:this.head=t,this.tail=t,++this.length}},{key:"unshift",value:function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length}},{key:"shift",value:function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(e){if(0===this.length)return"";for(var t=this.head,r=""+t.data;t=t.next;)r+=e+t.data;return r}},{key:"concat",value:function(e){if(0===this.length)return s.alloc(0);for(var t,r,n,i=s.allocUnsafe(e>>>0),o=this.head,a=0;o;)t=o.data,r=i,n=a,s.prototype.copy.call(t,r,n),a+=o.data.length,o=o.next;return i}},{key:"consume",value:function(e,t){var r;return ei.length?i.length:e;if(o===i.length?n+=i:n+=i.slice(0,e),0===(e-=o)){o===i.length?(++r,t.next?this.head=t.next:this.head=this.tail=null):(this.head=t,t.data=i.slice(o));break}++r}return this.length-=r,n}},{key:"_getBuffer",value:function(e){var t=s.allocUnsafe(e),r=this.head,n=1;for(r.data.copy(t),e-=r.data.length;r=r.next;){var i=r.data,o=e>i.length?i.length:e;if(i.copy(t,t.length-e,0,o),0===(e-=o)){o===i.length?(++n,r.next?this.head=r.next:this.head=this.tail=null):(this.head=r,r.data=i.slice(o));break}++n}return this.length-=n,t}},{key:l,value:function(e,t){return a(this,function(e){for(var t=1;t0,function(e){c||(c=e),e&&f.forEach(l),o||(f.forEach(l),h(c))})});return r.reduce(u)}},{"../../../errors":55,"./end-of-stream":64}],67:[function(e,t,r){"use strict";var n=e("../../../errors").codes.ERR_INVALID_OPT_VALUE;t.exports={getHighWaterMark:function(e,t,r,i){var o=function(e,t,r){return null!=e.highWaterMark?e.highWaterMark:t?e[r]:null}(t,i,r);if(null!=o){if(!isFinite(o)||Math.floor(o)!==o||o<0)throw new n(i?r:"highWaterMark",o);return Math.floor(o)}return e.objectMode?16:16384}}},{"../../../errors":55}],68:[function(e,t,r){t.exports=e("events").EventEmitter},{events:22}],69:[function(e,t,r){(r=t.exports=e("./lib/_stream_readable.js")).Stream=r,r.Readable=r,r.Writable=e("./lib/_stream_writable.js"),r.Duplex=e("./lib/_stream_duplex.js"),r.Transform=e("./lib/_stream_transform.js"),r.PassThrough=e("./lib/_stream_passthrough.js"),r.finished=e("./lib/internal/streams/end-of-stream.js"),r.pipeline=e("./lib/internal/streams/pipeline.js")},{"./lib/_stream_duplex.js":56,"./lib/_stream_passthrough.js":57,"./lib/_stream_readable.js":58,"./lib/_stream_transform.js":59,"./lib/_stream_writable.js":60,"./lib/internal/streams/end-of-stream.js":64,"./lib/internal/streams/pipeline.js":66}],70:[function(e,t,r){"use strict";t.exports=function(){if("function"!=typeof arguments[0])throw new Error("callback needed");if("number"!=typeof arguments[1])throw new Error("interval needed");var e;if(arguments.length>0){e=new Array(arguments.length-2);for(var t=0;t>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function a(e){var t=this.lastTotal-this.lastNeed,r=function(e,t,r){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==r?r:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function l(e,t){if((e.length-t)%2==0){var r=e.toString("utf16le",t);if(r){var n=r.charCodeAt(r.length-1);if(n>=55296&&n<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function u(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,r)}return t}function c(e,t){var r=(e.length-t)%3;return 0===r?e.toString("base64",t):(this.lastNeed=3-r,this.lastTotal=3,1===r?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-r))}function h(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function f(e){return e.toString(this.encoding)}function p(e){return e&&e.length?this.write(e):""}r.StringDecoder=o,o.prototype.write=function(e){if(0===e.length)return"";var t,r;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";r=this.lastNeed,this.lastNeed=0}else r=0;return r=0)return i>0&&(e.lastNeed=i-1),i;if(--n=0)return i>0&&(e.lastNeed=i-2),i;if(--n=0)return i>0&&(2===i?i=0:e.lastNeed=i-3),i;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=r;var n=e.length-(r-this.lastNeed);return e.copy(this.lastChar,0,n),e.toString("utf8",t,n)},o.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},{"safe-buffer":73}],76:[function(e,t,r){"use strict";var n=e("punycode"),i=e("./util");function o(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}r.parse=v,r.resolve=function(e,t){return v(e,!1,!0).resolve(t)},r.resolveObject=function(e,t){return e?v(e,!1,!0).resolveObject(t):t},r.format=function(e){i.isString(e)&&(e=v(e));return e instanceof o?e.format():o.prototype.format.call(e)},r.Url=o;var s=/^([a-z0-9.+-]+:)/i,a=/:[0-9]*$/,l=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,u=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),c=["'"].concat(u),h=["%","/","?",";","#"].concat(c),f=["/","?","#"],p=/^[+a-z0-9A-Z_-]{0,63}$/,d=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,g={javascript:!0,"javascript:":!0},y={javascript:!0,"javascript:":!0},b={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},m=e("querystring");function v(e,t,r){if(e&&i.isObject(e)&&e instanceof o)return e;var n=new o;return n.parse(e,t,r),n}o.prototype.parse=function(e,t,r){if(!i.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var o=e.indexOf("?"),a=-1!==o&&o127?R+="x":R+=B[M];if(!R.match(p)){var L=P.slice(0,x),j=P.slice(x+1),U=B.match(d);U&&(L.push(U[1]),j.unshift(U[2])),j.length&&(v="/"+j.join(".")+v),this.hostname=L.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),I||(this.hostname=n.toASCII(this.hostname));var q=this.port?":"+this.port:"",D=this.hostname||"";this.host=D+q,this.href+=this.host,I&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==v[0]&&(v="/"+v))}if(!g[k])for(x=0,O=c.length;x0)&&r.host.split("@"))&&(r.auth=I.shift(),r.host=r.hostname=I.shift());return r.search=e.search,r.query=e.query,i.isNull(r.pathname)&&i.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.href=r.format(),r}if(!S.length)return r.pathname=null,r.search?r.path="/"+r.search:r.path=null,r.href=r.format(),r;for(var C=S.slice(-1)[0],T=(r.host||e.host||S.length>1)&&("."===C||".."===C)||""===C,x=0,A=S.length;A>=0;A--)"."===(C=S[A])?S.splice(A,1):".."===C?(S.splice(A,1),x++):x&&(S.splice(A,1),x--);if(!_&&!k)for(;x--;x)S.unshift("..");!_||""===S[0]||S[0]&&"/"===S[0].charAt(0)||S.unshift(""),T&&"/"!==S.join("/").substr(-1)&&S.push("");var I,P=""===S[0]||S[0]&&"/"===S[0].charAt(0);E&&(r.hostname=r.host=P?"":S.length?S.shift():"",(I=!!(r.host&&r.host.indexOf("@")>0)&&r.host.split("@"))&&(r.auth=I.shift(),r.host=r.hostname=I.shift()));return(_=_||r.host&&S.length)&&!P&&S.unshift(""),S.length?r.pathname=S.join("/"):(r.pathname=null,r.path=null),i.isNull(r.pathname)&&i.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.auth=e.auth||r.auth,r.slashes=r.slashes||e.slashes,r.href=r.format(),r},o.prototype.parseHost=function(){var e=this.host,t=a.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},{"./util":77,punycode:51,querystring:54}],77:[function(e,t,r){"use strict";t.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},{}],78:[function(e,t,r){(function(e){(function(){function r(t){try{if(!e.localStorage)return!1}catch(e){return!1}var r=e.localStorage[t];return null!=r&&"true"===String(r).toLowerCase()}t.exports=function(e,t){if(r("noDeprecation"))return e;var n=!1;return function(){if(!n){if(r("throwDeprecation"))throw new Error(t);r("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}}}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],79:[function(e,t,r){t.exports=function e(t,r){if(t&&r)return e(t)(r);if("function"!=typeof t)throw new TypeError("need wrapper function");Object.keys(t).forEach(function(e){n[e]=t[e]});return n;function n(){for(var e=new Array(arguments.length),r=0;r0)for(var r=0,i=arguments.length;r1)r=t;else{if(!this.head)throw new TypeError("Reduce of empty list with no initial value");n=this.head.next,r=this.head.value}for(var i=0;null!==n;i++)r=e(r,n.value,i),n=n.next;return r},n.prototype.reduceReverse=function(e,t){var r,n=this.tail;if(arguments.length>1)r=t;else{if(!this.tail)throw new TypeError("Reduce of empty list with no initial value");n=this.tail.prev,r=this.tail.value}for(var i=this.length-1;null!==n;i--)r=e(r,n.value,i),n=n.prev;return r},n.prototype.toArray=function(){for(var e=new Array(this.length),t=0,r=this.head;null!==r;t++)e[t]=r.value,r=r.next;return e},n.prototype.toArrayReverse=function(){for(var e=new Array(this.length),t=0,r=this.tail;null!==r;t++)e[t]=r.value,r=r.prev;return e},n.prototype.slice=function(e,t){(t=t||this.length)<0&&(t+=this.length),(e=e||0)<0&&(e+=this.length);var r=new n;if(tthis.length&&(t=this.length);for(var i=0,o=this.head;null!==o&&ithis.length&&(t=this.length);for(var i=this.length,o=this.tail;null!==o&&i>t;i--)o=o.prev;for(;null!==o&&i>e;i--,o=o.prev)r.push(o.value);return r},n.prototype.splice=function(e,t,...r){e>this.length&&(e=this.length-1),e<0&&(e=this.length+e);for(var n=0,o=this.head;null!==o&&n wx.navigateBack(), 500); + return; + } + + // 从全局或存储获取设备信息 + let device = app.globalData.currentDevice; + if (!device || device.serialNumber !== sn) { + device = storage.getDevice(sn); + } + + if (!device) { + wx.showToast({ title: '设备不存在', icon: 'none' }); + setTimeout(() => wx.navigateBack(), 500); + return; + } + + this.setData({ device }); + + // 监听 BLE 状态变化 + ble.bleService.onStateChange((state) => { + const connected = state === ble.BLE_STATE.READY || state === ble.BLE_STATE.CONNECTED; + this.setData({ bleConnected: connected }); + }); + + // 监听 BLE 数据 + ble.bleService.onData((data) => { + this._handleBleData(data); + }); + + // 监听 MQTT 状态变化 + mqtt.mqttClient.onStateChange((state) => { + const connected = state === mqtt.MQTT_STATE.CONNECTED; + this.setData({ mqttConnected: connected }); + }); + }, + + onUnload() { + // 清除回调,防止页面销毁后 setData 报错 + ble.bleService.clearCallbacks(); + mqtt.mqttClient.clearCallbacks(); + + // 断开连接 + ble.bleService.disconnect(); + mqtt.mqttClient.disconnect(); + }, + + /** + * 处理 BLE 数据 + */ + _handleBleData(data) { + const now = new Date(); + const time = this._formatTime(now); + + // 使用唯一 ID 标识数据项(防止重复数据更新错乱) + const dataItem = { + id: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, + hex: data.hex, + time: time, + uploaded: false, + }; + + let dataList = this.data.dataList; + dataList.unshift(dataItem); + + // 限制数量 + if (dataList.length > this.data.maxDataCount) { + dataList = dataList.slice(0, this.data.maxDataCount); + } + + this.setData({ + dataList, + 'stats.received': this.data.stats.received + 1, + }); + + // 如果 MQTT 已连接且设备已启用,自动上报 + if (this.data.mqttConnected && this.data.device.enabled) { + this._uploadData(data.hex, dataItem.id); + } else { + // MQTT 未连接,缓存数据(离线缓存) + storage.cachePendingData({ + deviceId: this.data.device.serialNumber, + productId: this.data.device.productId, + hex: data.hex, + dataId: dataItem.id, + }); + } + }, + + /** + * 上报数据到 yack + * @param {string} hexData - 十六进制数据 + * @param {string} [dataId] - 数据项 ID(用于更新列表状态) + * @throws 上报失败时抛出异常 + */ + async _uploadData(hexData, dataId) { + const device = this.data.device; + if (!device) return; + + const topic = auth.buildPropertyPostTopic(device.productId, device.serialNumber); + + try { + await mqtt.mqttClient.publish(topic, hexData); + console.log('[Detail] 上报成功:', hexData); + + // 更新状态 + this.setData({ + 'stats.uploaded': this.data.stats.uploaded + 1, + }); + + // 更新 dataList 中的状态(按唯一 ID 查找) + if (dataId) { + const dataList = this.data.dataList; + const index = dataList.findIndex(d => d.id === dataId); + if (index >= 0) { + this.setData({ + [`dataList[${index}].uploaded`]: true, + }); + } + } + + } catch (err) { + console.error('[Detail] 上报失败:', err); + this.setData({ + 'stats.failed': this.data.stats.failed + 1, + }); + // 抛出异常,供调用方统计失败 + throw err; + } + }, + + /** + * 连接/断开 BLE + */ + async handleConnect() { + const { device, bleConnected } = this.data; + if (!device) return; + + if (bleConnected) { + // 断开连接 + await ble.bleService.disconnect(); + this.setData({ bleConnected: false }); + return; + } + + this.setData({ connecting: true }); + + try { + // 初始化蓝牙 + await ble.bleService.initBluetooth(); + // 连接设备 + await ble.bleService.connectDevice(device.deviceId, device.serviceUUID); + this.setData({ bleConnected: true }); + wx.showToast({ title: 'BLE 已连接', icon: 'success' }); + } catch (err) { + console.error('[Detail] BLE 连接失败:', err); + wx.showToast({ + title: err.message || 'BLE 连接失败', + icon: 'none', + }); + } finally { + this.setData({ connecting: false }); + } + }, + + /** + * 连接/断开 MQTT + */ + async handleMqttConnect() { + const { device, mqttConnected } = this.data; + if (!device) return; + + if (mqttConnected) { + // 断开连接 + await mqtt.mqttClient.disconnect(); + this.setData({ mqttConnected: false }); + return; + } + + this.setData({ mqttConnecting: true }); + + try { + // 获取 MQTT 凭证 + const credentials = auth.getMqttCredentialsFromDevice(device); + if (!credentials) { + throw new Error('获取 MQTT 凭证失败'); + } + + // 连接 MQTT + await mqtt.mqttClient.connect(credentials); + this.setData({ mqttConnected: true }); + wx.showToast({ title: 'MQTT 已连接', icon: 'success' }); + + // 上传缓存的离线数据 + this._uploadPendingData(); + + } catch (err) { + console.error('[Detail] MQTT 连接失败:', err); + wx.showToast({ + title: err.message || 'MQTT 连接失败', + icon: 'none', + }); + } finally { + this.setData({ mqttConnecting: false }); + } + }, + + /** + * 上传缓存的离线数据 + */ + async _uploadPendingData() { + const pendingList = storage.getPendingData(); + if (pendingList.length === 0) return; + + console.log('[Detail] 开始上传离线缓存数据:', pendingList.length, '条'); + const device = this.data.device; + + let uploaded = 0; + let failed = 0; + + for (const item of pendingList) { + // 只上传当前设备的数据 + if (item.deviceId === device.serialNumber) { + try { + const topic = auth.buildPropertyPostTopic(device.productId, device.serialNumber); + await mqtt.mqttClient.publish(topic, item.hex); + uploaded++; + } catch (err) { + console.error('[Detail] 离线数据上传失败:', item.hex, err); + failed++; + } + } + } + + // 清除已处理的缓存数据 + storage.clearPendingData(); + + if (uploaded > 0 || failed > 0) { + console.log('[Detail] 离线数据上传完成:', uploaded, '成功,', failed, '失败'); + } + }, + + /** + * 上报单条数据 + */ + async handleUploadSingle(e) { + const index = e.currentTarget.dataset.index; + const data = this.data.dataList[index]; + if (!data || data.uploaded) return; + + if (!this.data.mqttConnected) { + wx.showToast({ title: 'MQTT 未连接', icon: 'none' }); + return; + } + + try { + await this._uploadData(data.hex, data.id); + } catch (err) { + wx.showToast({ title: err.message || '上报失败', icon: 'none' }); + } + }, + + /** + * 全部上报 + */ + async handleUploadAll() { + const { dataList, mqttConnected } = this.data; + if (!mqttConnected || dataList.length === 0) return; + + wx.showLoading({ title: '上报中...', mask: true }); + + let uploaded = 0; + let failed = 0; + + for (const data of dataList) { + if (!data.uploaded) { + try { + await this._uploadData(data.hex, data.id); + uploaded++; + } catch (err) { + failed++; + } + } + } + + wx.hideLoading(); + + wx.showToast({ + title: `上报完成: ${uploaded} 成功, ${failed} 失败`, + icon: 'none', + }); + }, + + /** + * 清空数据 + */ + handleClearData() { + wx.showModal({ + title: '清空数据', + content: '确定清空所有接收到的数据吗?', + success: (res) => { + if (res.confirm) { + this.setData({ + dataList: [], + stats: { received: 0, uploaded: 0, failed: 0 }, + }); + } + }, + }); + }, + + /** + * 格式化时间 + */ + _formatTime(date) { + const h = date.getHours().toString().padStart(2, '0'); + const m = date.getMinutes().toString().padStart(2, '0'); + const s = date.getSeconds().toString().padStart(2, '0'); + return `${h}:${m}:${s}`; + }, +}); \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/detail/detail.json b/yack-mini/miniprogram/pages/device/detail/detail.json new file mode 100644 index 00000000..24e32703 --- /dev/null +++ b/yack-mini/miniprogram/pages/device/detail/detail.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "设备详情", + "enablePullDownRefresh": false, + "usingComponents": {} +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/detail/detail.wxml b/yack-mini/miniprogram/pages/device/detail/detail.wxml new file mode 100644 index 00000000..e4e9447f --- /dev/null +++ b/yack-mini/miniprogram/pages/device/detail/detail.wxml @@ -0,0 +1,91 @@ + + + + + + 设备名称 + {{device.name || '-'}} + + + 设备编号 + {{device.serialNumber || '-'}} + + + 产品ID + {{device.productId || '-'}} + + + BLE 地址 + {{device.deviceId || '-'}} + + + BLE 状态 + + {{bleConnected ? '已连接' : '未连接'}} + + + + MQTT 状态 + + {{mqttConnected ? '已连接' : '未连接'}} + + + + + + + + 实时数据 + 清空 + + + + + {{item.time}} + {{item.hex}} + + 已上报 + + + 上报 + + + + + + 暂无数据 + 连接 BLE 设备后,数据将在此显示 + + + + + + + {{stats.received}} + 接收数据 + + + {{stats.uploaded}} + 已上报 + + + {{stats.failed}} + 失败 + + + + + + + + + + + + \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/detail/detail.wxss b/yack-mini/miniprogram/pages/device/detail/detail.wxss new file mode 100644 index 00000000..6518c601 --- /dev/null +++ b/yack-mini/miniprogram/pages/device/detail/detail.wxss @@ -0,0 +1,175 @@ +/* 设备详情页样式 */ +.device-info { + margin-bottom: 20rpx; +} + +.info-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 16rpx 0; + border-bottom: 1rpx solid #f5f5f5; +} + +.info-row:last-child { + border-bottom: none; +} + +.info-label { + font-size: 26rpx; + color: #666; +} + +.info-value { + font-size: 26rpx; + color: #333; +} + +.data-section { + margin-bottom: 20rpx; +} + +.section-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin-bottom: 16rpx; +} + +.section-title { + font-size: 30rpx; + font-weight: 600; + color: #333; +} + +.clear-btn { + font-size: 24rpx; + color: #ff4d4f; +} + +.data-list { + max-height: 600rpx; + overflow-y: auto; +} + +.data-item { + display: flex; + flex-direction: row; + align-items: center; + padding: 16rpx 0; + border-bottom: 1rpx solid #f5f5f5; +} + +.data-item:last-child { + border-bottom: none; +} + +.data-time { + width: 140rpx; + font-size: 24rpx; + color: #999; +} + +.data-hex { + flex: 1; + font-size: 24rpx; + color: #333; + font-family: monospace; + word-break: break-all; +} + +.data-action { + width: 100rpx; + text-align: right; +} + +.uploaded { + font-size: 22rpx; + color: #52c41a; +} + +.upload-btn { + font-size: 22rpx; + color: #1890ff; +} + +.empty-hint { + font-size: 24rpx; + color: #bbb; + margin-top: 12rpx; +} + +.stats-section { + display: flex; + flex-direction: row; + margin-bottom: 20rpx; +} + +.stat-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 16rpx 0; +} + +.stat-value { + font-size: 40rpx; + font-weight: 600; + color: #1890ff; +} + +.stat-label { + font-size: 24rpx; + color: #999; + margin-top: 8rpx; +} + +.action-group { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16rpx; + padding: 20rpx 0; +} + +.btn-connect, +.btn-mqtt, +.btn-upload { + flex: 1; + min-width: 200rpx; + height: 80rpx; + line-height: 80rpx; + font-size: 28rpx; + border-radius: 8rpx; + border: none; +} + +.btn-connect { + background-color: #1890ff; + color: #fff; +} + +.btn-connect[disabled] { + background-color: #69c0ff; +} + +.btn-mqtt { + background-color: #52c41a; + color: #fff; +} + +.btn-mqtt[disabled] { + background-color: #95de64; +} + +.btn-upload { + background-color: #722ed1; + color: #fff; +} + +.btn-upload[disabled] { + background-color: #b37feb; +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/scan/scan.js b/yack-mini/miniprogram/pages/device/scan/scan.js new file mode 100644 index 00000000..c0fd0762 --- /dev/null +++ b/yack-mini/miniprogram/pages/device/scan/scan.js @@ -0,0 +1,220 @@ +/** + * 扫描设备页面 + */ +const ble = require('../../../services/ble.js'); +const storage = require('../../../utils/storage.js'); + +Page({ + data: { + scanning: false, + scannedDevices: [], + statusText: '准备扫描', + statusHint: '点击下方按钮开始扫描 BLE 设备', + + // 配置弹窗 + showConfigModal: false, + configuring: false, + selectedDevice: null, + configForm: { + name: '', + serialNumber: '', + productId: '', + productSecret: '', + serviceUUID: '', + }, + }, + + onLoad() { + // 初始化蓝牙 + this._initBluetooth(); + }, + + onUnload() { + // 停止扫描 + if (this.data.scanning) { + ble.bleService.stopScan(); + } + }, + + /** + * 初始化蓝牙 + */ + async _initBluetooth() { + try { + await ble.bleService.initBluetooth(); + this.setData({ + statusText: '蓝牙已就绪', + statusHint: '点击下方按钮开始扫描 BLE 设备', + }); + } catch (err) { + console.error('[Scan] 初始化蓝牙失败:', err); + this.setData({ + statusText: '蓝牙初始化失败', + statusHint: err.message || '请确保已开启蓝牙', + }); + wx.showModal({ + title: '蓝牙错误', + content: err.message || '请确保已开启蓝牙权限', + showCancel: false, + success: () => { + wx.navigateBack(); + }, + }); + } + }, + + /** + * 开始扫描 + */ + async handleScan() { + if (this.data.scanning) return; + + this.setData({ + scanning: true, + scannedDevices: [], + statusText: '正在扫描...', + statusHint: '请确保设备已开机且在附近', + }); + + try { + const devices = await ble.bleService.scanDevices(); + this.setData({ + scanning: false, + scannedDevices: devices, + statusText: devices.length > 0 ? `发现 ${devices.length} 个设备` : '扫描完成', + statusHint: devices.length > 0 ? '点击设备进行连接配置' : '未发现设备,请确保设备已开机', + }); + } catch (err) { + console.error('[Scan] 扫描失败:', err); + this.setData({ + scanning: false, + statusText: '扫描失败', + statusHint: err.message || '请重试', + }); + } + }, + + /** + * 停止扫描 + */ + async handleStopScan() { + await ble.bleService.stopScan(); + this.setData({ + scanning: false, + statusText: '已停止扫描', + }); + }, + + /** + * 选择设备 - 显示配置弹窗 + */ + handleSelectDevice(e) { + const index = e.currentTarget.dataset.index; + const device = this.data.scannedDevices[index]; + if (!device) return; + + this.setData({ + selectedDevice: device, + showConfigModal: true, + configForm: { + name: device.name || '', + serialNumber: '', + productId: '', + productSecret: '', + serviceUUID: '', + }, + }); + }, + + /** + * 配置输入 + */ + onConfigInput(e) { + const field = e.currentTarget.dataset.field; + const value = e.detail.value; + this.setData({ + [`configForm.${field}`]: value, + }); + }, + + /** + * 取消配置 + */ + handleCancelConfig() { + this.setData({ + showConfigModal: false, + selectedDevice: null, + }); + }, + + /** + * 确认配置 - 连接设备并保存 + */ + async handleConfirmConfig() { + const { selectedDevice, configForm } = this.data; + + // 验证配置 + if (!configForm.serialNumber) { + wx.showToast({ title: '请输入设备编号', icon: 'none' }); + return; + } + if (!configForm.productId) { + wx.showToast({ title: '请输入产品ID', icon: 'none' }); + return; + } + if (!configForm.productSecret) { + wx.showToast({ title: '请输入产品密钥', icon: 'none' }); + return; + } + + this.setData({ configuring: true }); + + try { + // 连接设备 + wx.showLoading({ title: '正在连接...', mask: true }); + + const serviceId = configForm.serviceUUID || null; + await ble.bleService.connectDevice(selectedDevice.deviceId, serviceId); + + wx.hideLoading(); + + // 保存设备配置 + const deviceConfig = { + deviceId: selectedDevice.deviceId, + serialNumber: configForm.serialNumber, + name: configForm.name || selectedDevice.name || configForm.serialNumber, + productId: parseInt(configForm.productId), + productSecret: configForm.productSecret, + serviceUUID: configForm.serviceUUID, + bleName: selectedDevice.name, + RSSI: selectedDevice.RSSI, + enabled: true, + connected: true, + createTime: Date.now(), + }; + + storage.addDevice(deviceConfig); + + wx.showToast({ title: '保存成功', icon: 'success' }); + + // 断开 BLE 连接(让详情页重新连接,防止连接泄漏) + await ble.bleService.disconnect(); + + // 延迟返回 + setTimeout(() => { + this.setData({ showConfigModal: false }); + wx.navigateBack(); + }, 500); + + } catch (err) { + wx.hideLoading(); + console.error('[Scan] 连接失败:', err); + wx.showToast({ + title: err.message || '连接失败', + icon: 'none', + }); + } finally { + this.setData({ configuring: false }); + } + }, +}); \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/scan/scan.json b/yack-mini/miniprogram/pages/device/scan/scan.json new file mode 100644 index 00000000..7f4c55b9 --- /dev/null +++ b/yack-mini/miniprogram/pages/device/scan/scan.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "扫描设备", + "usingComponents": {} +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/scan/scan.wxml b/yack-mini/miniprogram/pages/device/scan/scan.wxml new file mode 100644 index 00000000..3c1e63e6 --- /dev/null +++ b/yack-mini/miniprogram/pages/device/scan/scan.wxml @@ -0,0 +1,86 @@ + + + + + + + + + {{statusText}} + {{statusHint}} + + + + + + 发现设备 ({{scannedDevices.length}}) + 点击连接并配置 + + + + + {{item.name || '未知设备'}} + {{item.deviceId}} + + + {{item.RSSI}} dBm + + + + + + + 未发现设备 + 请确保设备已开机且在附近 + + + + + + + + + + + + + + + 配置设备 + + + + + 设备名称 + + + + + 设备编号 (SN) + + + + + 产品ID + + + + + 产品密钥 + + + + + BLE 服务 UUID (可选) + + + + + + + + + + \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/device/scan/scan.wxss b/yack-mini/miniprogram/pages/device/scan/scan.wxss new file mode 100644 index 00000000..51b087b2 --- /dev/null +++ b/yack-mini/miniprogram/pages/device/scan/scan.wxss @@ -0,0 +1,231 @@ +/* 扫描页面样式 */ +.scan-status { + display: flex; + flex-direction: column; + align-items: center; + padding: 60rpx 40rpx; + margin-bottom: 20rpx; +} + +.status-icon { + position: relative; + width: 160rpx; + height: 160rpx; + margin-bottom: 32rpx; +} + +.ble-icon { + width: 100%; + height: 100%; +} + +.scan-animation { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 50%; + border: 4rpx solid #1890ff; + opacity: 0; +} + +.scan-animation.scanning { + animation: scan-wave 1.5s infinite; +} + +@keyframes scan-wave { + 0% { + transform: scale(1); + opacity: 1; + } + 100% { + transform: scale(1.5); + opacity: 0; + } +} + +.status-text { + font-size: 36rpx; + font-weight: 600; + color: #333; + margin-bottom: 12rpx; +} + +.status-hint { + font-size: 26rpx; + color: #999; +} + +.list-header { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 20rpx 0; + font-size: 28rpx; + color: #666; +} + +.hint { + color: #999; + font-size: 24rpx; +} + +.scanned-item { + display: flex; + flex-direction: row; + align-items: center; + background-color: #fff; + padding: 28rpx; + border-radius: 12rpx; + margin-bottom: 16rpx; +} + +.item-main { + flex: 1; + display: flex; + flex-direction: column; +} + +.item-name { + font-size: 30rpx; + font-weight: 600; + color: #333; + margin-bottom: 8rpx; +} + +.item-id { + font-size: 24rpx; + color: #999; +} + +.item-rssi { + font-size: 24rpx; + color: #1890ff; +} + +.empty-hint { + font-size: 24rpx; + color: #bbb; + margin-top: 12rpx; +} + +.btn-group { + display: flex; + flex-direction: row; + padding: 40rpx; + gap: 20rpx; +} + +.btn-scan { + flex: 1; + background-color: #1890ff; + color: #fff; + height: 88rpx; + line-height: 88rpx; + border-radius: 44rpx; + border: none; +} + +.btn-stop { + flex: 1; + background-color: #ff4d4f; + color: #fff; + height: 88rpx; + line-height: 88rpx; + border-radius: 44rpx; + border: none; +} + +/* 配置弹窗 */ +.config-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 999; +} + +.modal-mask { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-content { + position: absolute; + left: 40rpx; + right: 40rpx; + top: 50%; + transform: translateY(-50%); + background-color: #fff; + border-radius: 16rpx; + overflow: hidden; +} + +.modal-header { + padding: 32rpx; + border-bottom: 1rpx solid #f0f0f0; +} + +.modal-title { + font-size: 32rpx; + font-weight: 600; + color: #333; +} + +.modal-body { + padding: 32rpx; +} + +.config-item { + margin-bottom: 28rpx; +} + +.config-label { + display: block; + font-size: 26rpx; + color: #666; + margin-bottom: 12rpx; +} + +.config-input { + width: 100%; + height: 72rpx; + background-color: #f5f5f5; + border-radius: 8rpx; + padding: 0 20rpx; + font-size: 28rpx; + box-sizing: border-box; +} + +.modal-footer { + display: flex; + flex-direction: row; + border-top: 1rpx solid #f0f0f0; +} + +.btn-cancel, +.btn-confirm { + flex: 1; + height: 100rpx; + line-height: 100rpx; + font-size: 30rpx; + border: none; + border-radius: 0; + background-color: transparent; +} + +.btn-cancel { + color: #666; + border-right: 1rpx solid #f0f0f0; +} + +.btn-confirm { + color: #1890ff; + font-weight: 600; +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/index/index.js b/yack-mini/miniprogram/pages/index/index.js new file mode 100644 index 00000000..6fae2123 --- /dev/null +++ b/yack-mini/miniprogram/pages/index/index.js @@ -0,0 +1,119 @@ +/** + * 首页 - 设备列表 + */ +const storage = require('../../utils/storage.js'); +const app = getApp(); + +Page({ + data: { + userInfo: null, + devices: [], + }, + + onLoad() { + // 未登录跳转登录页 + if (!app.isLoggedIn()) { + wx.reLaunch({ url: '/pages/login/login' }); + return; + } + }, + + onShow() { + // 每次显示时刷新设备列表 + this.refreshData(); + }, + + /** + * 刷新数据 + */ + refreshData() { + const devices = storage.getDeviceList(); + this.setData({ + userInfo: app.globalData.userInfo, + devices: devices, + }); + }, + + /** + * 点击设备卡片 - 跳转详情页 + */ + handleDeviceTap(e) { + const index = e.currentTarget.dataset.index; + const device = this.data.devices[index]; + if (!device) return; + + // 保存当前设备到全局 + app.globalData.currentDevice = device; + + wx.navigateTo({ + url: `/pages/device/detail/detail?sn=${device.serialNumber}`, + }); + }, + + /** + * 删除设备 + */ + handleDeviceDelete(e) { + const serialNumber = e.detail.serialNumber; + wx.showModal({ + title: '删除设备', + content: '确定删除该设备配置吗?', + confirmColor: '#ff4d4f', + success: (res) => { + if (res.confirm) { + storage.removeDevice(serialNumber); + this.refreshData(); + wx.showToast({ title: '已删除', icon: 'success' }); + } + }, + }); + }, + + /** + * 设备启停切换 + */ + handleDeviceSwitch(e) { + const serialNumber = e.detail.serialNumber; + const enabled = e.detail.enabled; + const devices = this.data.devices; + const index = devices.findIndex(d => d.serialNumber === serialNumber); + if (index >= 0) { + devices[index].enabled = enabled; + this.setData({ devices }); + storage.updateDevice(devices[index]); + } + }, + + /** + * 添加设备 - 跳转扫描页 + */ + handleAddDevice() { + wx.navigateTo({ + url: '/pages/device/scan/scan', + }); + }, + + /** + * 退出登录 + */ + handleLogout() { + wx.showModal({ + title: '退出登录', + content: '确定要退出登录吗?', + success: (res) => { + if (res.confirm) { + app.clearLoginInfo(); + wx.reLaunch({ url: '/pages/login/login' }); + } + }, + }); + }, + + /** + * 下拉刷新 + */ + onPullDownRefresh() { + this.refreshData(); + wx.stopPullDownRefresh(); + }, +}); \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/index/index.json b/yack-mini/miniprogram/pages/index/index.json new file mode 100644 index 00000000..e0806830 --- /dev/null +++ b/yack-mini/miniprogram/pages/index/index.json @@ -0,0 +1,7 @@ +{ + "navigationBarTitleText": "我的设备", + "enablePullDownRefresh": false, + "usingComponents": { + "device-card": "/components/device-card/device-card" + } +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/index/index.wxml b/yack-mini/miniprogram/pages/index/index.wxml new file mode 100644 index 00000000..a4dc5cf6 --- /dev/null +++ b/yack-mini/miniprogram/pages/index/index.wxml @@ -0,0 +1,39 @@ + + + + + + + 退出 + + + + + + + + + + + 暂无设备 + 点击下方按钮扫描并添加 BLE 设备 + + + + + + + \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/index/index.wxss b/yack-mini/miniprogram/pages/index/index.wxss new file mode 100644 index 00000000..434f62b3 --- /dev/null +++ b/yack-mini/miniprogram/pages/index/index.wxss @@ -0,0 +1,73 @@ +/* 首页样式 */ +.user-bar { + display: flex; + flex-direction: row; + align-items: center; +} + +.user-avatar { + width: 88rpx; + height: 88rpx; + border-radius: 50%; + background-color: #f0f0f0; + margin-right: 24rpx; +} + +.user-info { + flex: 1; + display: flex; + flex-direction: column; +} + +.user-name { + font-size: 32rpx; + font-weight: 600; + color: #333; +} + +.user-id { + font-size: 24rpx; + color: #999; + margin-top: 4rpx; +} + +.logout-btn { + font-size: 26rpx; + color: #ff4d4f; + padding: 8rpx 24rpx; + border: 1rpx solid #ff4d4f; + border-radius: 28rpx; +} + +.device-list { + margin-top: 20rpx; +} + +.empty-subtitle { + font-size: 24rpx; + color: #bbb; + margin-top: 12rpx; +} + +.add-btn-wrapper { + position: fixed; + left: 0; + right: 0; + bottom: 60rpx; + padding: 0 40rpx; +} + +.add-btn { + background-color: #1890ff; + color: #fff; + height: 92rpx; + line-height: 92rpx; + font-size: 32rpx; + border-radius: 46rpx; + border: none; + box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.4); +} + +.add-btn:active { + background-color: #096dd9; +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/login/login.js b/yack-mini/miniprogram/pages/login/login.js new file mode 100644 index 00000000..0b67b5cc --- /dev/null +++ b/yack-mini/miniprogram/pages/login/login.js @@ -0,0 +1,146 @@ +/** + * 登录页面 + */ +const request = require('../../utils/request.js'); +const storage = require('../../utils/storage.js'); + +const app = getApp(); + +Page({ + data: { + username: '', + password: '', + captchaCode: '', + captchaUuid: '', + captchaImg: '', + captchaEnabled: false, + loading: false, + }, + + onLoad() { + // 检查是否已登录 + if (app.isLoggedIn()) { + this._goToIndex(); + return; + } + // 获取验证码配置 + this._checkCaptcha(); + }, + + /** + * 检查验证码是否启用 + */ + async _checkCaptcha() { + try { + const res = await request.captchaImage(); + if (res.img) { + this.setData({ + captchaEnabled: true, + captchaImg: 'data:image/png;base64,' + res.img, + captchaUuid: res.uuid, + }); + } + } catch (err) { + console.log('[Login] 验证码获取失败,可能未启用'); + } + }, + + /** + * 刷新验证码 + */ + refreshCaptcha() { + this._checkCaptcha(); + }, + + /** + * 用户名输入 + */ + onUsernameInput(e) { + this.setData({ username: e.detail.value }); + }, + + /** + * 密码输入 + */ + onPasswordInput(e) { + this.setData({ password: e.detail.value }); + }, + + /** + * 验证码输入 + */ + onCaptchaInput(e) { + this.setData({ captchaCode: e.detail.value }); + }, + + /** + * 登录 + */ + async handleLogin() { + const { username, password, captchaCode, captchaUuid, captchaEnabled } = this.data; + + // 验证输入 + if (!username) { + wx.showToast({ title: '请输入用户名', icon: 'none' }); + return; + } + if (!password) { + wx.showToast({ title: '请输入密码', icon: 'none' }); + return; + } + if (captchaEnabled && !captchaCode) { + wx.showToast({ title: '请输入验证码', icon: 'none' }); + return; + } + + this.setData({ loading: true }); + + try { + // 登录请求 + const loginRes = await request.login(username, password, captchaCode, captchaUuid); + const token = loginRes.token; + + // 获取用户信息 + const infoRes = await request.getInfo(); + const user = infoRes.user; + + // 保存登录信息 + app.setLoginInfo({ + token: token, + userId: user.userId, + userName: user.userName, + phonenumber: user.phonenumber, + avatar: user.avatar, + }); + + wx.showToast({ title: '登录成功', icon: 'success' }); + + // 跳转首页 + setTimeout(() => { + this._goToIndex(); + }, 500); + + } catch (err) { + console.error('[Login] 登录失败:', err); + wx.showToast({ + title: err.message || '登录失败', + icon: 'none', + }); + // 刷新验证码 + if (this.data.captchaEnabled) { + this._checkCaptcha(); + } + } finally { + this.setData({ loading: false }); + } + }, + + /** + * 跳转首页 + */ + _goToIndex() { + wx.reLaunch({ + url: '/pages/index/index', + }); + }, +}); \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/login/login.json b/yack-mini/miniprogram/pages/login/login.json new file mode 100644 index 00000000..e301e431 --- /dev/null +++ b/yack-mini/miniprogram/pages/login/login.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "登录", + "usingComponents": {} +} \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/login/login.wxml b/yack-mini/miniprogram/pages/login/login.wxml new file mode 100644 index 00000000..60a12972 --- /dev/null +++ b/yack-mini/miniprogram/pages/login/login.wxml @@ -0,0 +1,40 @@ + + \ No newline at end of file diff --git a/yack-mini/miniprogram/pages/login/login.wxss b/yack-mini/miniprogram/pages/login/login.wxss new file mode 100644 index 00000000..d9a35d2e --- /dev/null +++ b/yack-mini/miniprogram/pages/login/login.wxss @@ -0,0 +1,106 @@ +/* 登录页面样式 */ +.login-container { + display: flex; + flex-direction: column; + min-height: 100vh; + background-color: #fff; + padding: 80rpx 60rpx; +} + +.login-header { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 80rpx; +} + +.logo { + width: 160rpx; + height: 160rpx; + margin-bottom: 32rpx; +} + +.title { + font-size: 40rpx; + font-weight: 600; + color: #333; + margin-bottom: 16rpx; +} + +.subtitle { + font-size: 26rpx; + color: #999; +} + +.login-form { + flex: 1; +} + +.form-item { + margin-bottom: 32rpx; +} + +.label { + display: block; + font-size: 28rpx; + color: #666; + margin-bottom: 16rpx; +} + +.input { + width: 100%; + height: 88rpx; + background-color: #f5f5f5; + border-radius: 8rpx; + padding: 0 24rpx; + font-size: 30rpx; + box-sizing: border-box; +} + +.captcha-row { + display: flex; + flex-direction: row; + align-items: center; +} + +.captcha-input { + flex: 1; + margin-right: 20rpx; +} + +.captcha-img { + width: 200rpx; + height: 88rpx; + border-radius: 8rpx; + background-color: #f0f0f0; +} + +.btn-login { + width: 100%; + height: 88rpx; + background-color: #1890ff; + color: #fff; + font-size: 32rpx; + border-radius: 8rpx; + margin-top: 40rpx; + border: none; +} + +.btn-login:active { + background-color: #096dd9; +} + +.btn-login[disabled] { + background-color: #69c0ff; +} + +.login-footer { + display: flex; + justify-content: center; + padding-top: 60rpx; +} + +.tip { + font-size: 24rpx; + color: #999; +} \ No newline at end of file diff --git a/yack-mini/miniprogram/services/ble.js b/yack-mini/miniprogram/services/ble.js new file mode 100644 index 00000000..9e25e57b --- /dev/null +++ b/yack-mini/miniprogram/services/ble.js @@ -0,0 +1,511 @@ +/** + * 蓝牙 BLE 采集服务 + */ +const hex = require('../utils/hex.js'); + +// BLE 状态常量 +const BLE_STATE = { + DISCONNECTED: 'disconnected', + CONNECTING: 'connecting', + CONNECTED: 'connected', + DISCOVERING: 'discovering', + READY: 'ready', +}; + +// 默认配置 +const DEFAULT_CONFIG = { + // 扫描超时时间(毫秒) + scanTimeout: 10000, + // 连接超时时间(毫秒) + connectTimeout: 10000, + // 默认服务 UUID(需根据实际设备配置) + serviceUUIDs: [], +}; + +class BleService { + constructor() { + this.state = BLE_STATE.DISCONNECTED; + this.deviceId = null; + this.serviceId = null; + this.notifyCharacteristicId = null; + this.writeCharacteristicId = null; + this.onDataCallback = null; + this.onStateChangeCallback = null; + this.config = { ...DEFAULT_CONFIG }; + // 监听器注册标志(防止叠加) + this._listenersRegistered = false; + } + + /** + * 设置配置 + * @param {Object} config + */ + setConfig(config) { + this.config = { ...DEFAULT_CONFIG, ...config }; + } + + /** + * 设置数据回调 + * @param {Function} callback - (data: {hex, buffer}) => void + */ + onData(callback) { + this.onDataCallback = callback; + } + + /** + * 设置状态变化回调 + * @param {Function} callback - (state) => void + */ + onStateChange(callback) { + this.onStateChangeCallback = callback; + } + + /** + * 更新状态并通知 + * @param {string} state + */ + _setState(state) { + this.state = state; + if (this.onStateChangeCallback) { + this.onStateChangeCallback(state); + } + console.log('[BLE] 状态变更:', state); + } + + /** + * 初始化蓝牙适配器 + * @returns {Promise} + */ + initBluetooth() { + return new Promise((resolve, reject) => { + // 移除旧监听器,防止叠加(微信 API 支持注册多个回调) + if (this._listenersRegistered) { + try { + wx.offBLEConnectionStateChange(); + wx.offBLECharacteristicValueChange(); + } catch (e) { + // 忽略移除失败 + } + } + + wx.openBluetoothAdapter({ + success: () => { + console.log('[BLE] 初始化成功'); + + // 监听连接状态变化(只注册一次) + wx.onBLEConnectionStateChange((res) => { + console.log('[BLE] 连接状态变化:', res); + if (!res.connected) { + this._setState(BLE_STATE.DISCONNECTED); + this.deviceId = null; + } + }); + + // 监听特征值变化(只注册一次) + wx.onBLECharacteristicValueChange((res) => { + const hexData = hex.arrayBufferToHex(res.value); + console.log('[BLE] 收到数据:', hexData); + if (this.onDataCallback) { + this.onDataCallback({ + hex: hexData, + buffer: res.value, + deviceId: res.deviceId, + serviceId: res.serviceId, + characteristicId: res.characteristicId, + }); + } + }); + + this._listenersRegistered = true; + resolve(); + }, + fail: (err) => { + console.error('[BLE] 初始化失败:', err); + reject(new Error('蓝牙初始化失败: ' + (err.errMsg || '未知错误'))); + }, + }); + }); + } + + /** + * 扫描蓝牙设备 + * @param {Array} serviceUUIDs - 服务 UUID 列表(可选) + * @param {number} timeout - 超时时间(毫秒) + * @returns {Promise} 设备列表 + */ + scanDevices(serviceUUIDs = null, timeout = null) { + return new Promise((resolve, reject) => { + const uuids = serviceUUIDs || this.config.serviceUUIDs; + const scanTimeout = timeout || this.config.scanTimeout; + + const devices = []; + const deviceMap = new Map(); + + // 移除旧的扫描监听器,防止叠加 + try { + wx.offBluetoothDeviceFound(); + } catch (e) { + // 忽略移除失败 + } + + // 开始扫描 + wx.startBluetoothDevicesDiscovery({ + services: uuids.length > 0 ? uuids : undefined, + allowDuplicatesKey: false, + success: () => { + console.log('[BLE] 开始扫描, 超时:', scanTimeout); + // 监听扫描结果 + wx.onBluetoothDeviceFound((res) => { + res.devices.forEach((device) => { + // 去重 + if (!deviceMap.has(device.deviceId)) { + deviceMap.set(device.deviceId, true); + devices.push({ + deviceId: device.deviceId, + name: device.name || '未知设备', + localName: device.localName, + advertisData: device.advertisData, + RSSI: device.RSSI, + }); + } + }); + }); + + // 设置超时 + setTimeout(() => { + wx.stopBluetoothDevicesDiscovery({ + success: () => { + console.log('[BLE] 扫描完成, 发现设备:', devices.length); + // 移除扫描监听器 + try { + wx.offBluetoothDeviceFound(); + } catch (e) { + // 忽略 + } + resolve(devices); + }, + fail: () => { + try { + wx.offBluetoothDeviceFound(); + } catch (e) { + // 忽略 + } + resolve(devices); + }, + }); + }, scanTimeout); + }, + fail: (err) => { + console.error('[BLE] 扫描失败:', err); + reject(new Error('扫描失败: ' + (err.errMsg || '未知错误'))); + }, + }); + }); + } + + /** + * 停止扫描 + */ + stopScan() { + return new Promise((resolve) => { + wx.stopBluetoothDevicesDiscovery({ + success: () => { + console.log('[BLE] 已停止扫描'); + resolve(); + }, + fail: () => { + resolve(); + }, + }); + }); + } + + /** + * 连接设备 + * @param {string} deviceId - 设备 ID + * @param {string} serviceId - 服务 UUID(可选,不传则自动发现) + * @returns {Promise} + */ + connectDevice(deviceId, serviceId = null) { + return new Promise((resolve, reject) => { + this._setState(BLE_STATE.CONNECTING); + this.deviceId = deviceId; + + const connectTimeout = this.config.connectTimeout; + let timeoutId = null; + + // 设置连接超时 + timeoutId = setTimeout(() => { + wx.closeBLEConnection({ + deviceId: deviceId, + complete: () => { + this._setState(BLE_STATE.DISCONNECTED); + reject(new Error('连接超时')); + }, + }); + }, connectTimeout); + + // 发起连接 + wx.createBLEConnection({ + deviceId: deviceId, + success: () => { + clearTimeout(timeoutId); + console.log('[BLE] 连接成功:', deviceId); + this._setState(BLE_STATE.CONNECTED); + + // 获取 MTU(提高传输效率,iOS 最大 185,Android 建议 247) + wx.setBLEMTU({ + deviceId: deviceId, + mtu: 247, + complete: () => { + // 发现服务 + this._discoverServices(deviceId, serviceId) + .then(resolve) + .catch(reject); + }, + }); + }, + fail: (err) => { + clearTimeout(timeoutId); + this._setState(BLE_STATE.DISCONNECTED); + console.error('[BLE] 连接失败:', err); + reject(new Error('连接失败: ' + (err.errMsg || '未知错误'))); + }, + }); + }); + } + + /** + * 发现服务和特征值 + * @param {string} deviceId + * @param {string} serviceId + * @returns {Promise} + */ + _discoverServices(deviceId, serviceId = null) { + return new Promise((resolve, reject) => { + this._setState(BLE_STATE.DISCOVERING); + + wx.getBLEDeviceServices({ + deviceId: deviceId, + success: (res) => { + console.log('[BLE] 发现服务:', res.services); + const services = res.services; + + // 选择目标服务 + let targetService = null; + if (serviceId) { + // 指定了服务 UUID + targetService = services.find(s => s.uuid.toLowerCase().includes(serviceId.toLowerCase())); + } else { + // 自动选择:排除通用服务(GAP 0x1800, GATT 0x1801 等) + const GENERIC_SERVICE_PREFIXES = ['00001800', '00001801', '0000180a', '0000180f']; + targetService = services.find(s => + !GENERIC_SERVICE_PREFIXES.some(p => s.uuid.toLowerCase().startsWith(p)) + ); + // 如果没有非通用服务,选择第一个主服务 + if (!targetService) { + targetService = services.find(s => s.isPrimary) || services[0]; + } + } + + if (!targetService) { + reject(new Error('未找到目标服务')); + return; + } + + this.serviceId = targetService.uuid; + console.log('[BLE] 目标服务:', this.serviceId); + + // 获取特征值 + this._discoverCharacteristics(deviceId, this.serviceId) + .then(resolve) + .catch(reject); + }, + fail: (err) => { + console.error('[BLE] 获取服务失败:', err); + reject(new Error('获取服务失败')); + }, + }); + }); + } + + /** + * 发现特征值 + * @param {string} deviceId + * @param {string} serviceId + * @returns {Promise} + */ + _discoverCharacteristics(deviceId, serviceId) { + return new Promise((resolve, reject) => { + wx.getBLEDeviceCharacteristics({ + deviceId: deviceId, + serviceId: serviceId, + success: (res) => { + console.log('[BLE] 发现特征值:', res.characteristics); + const characteristics = res.characteristics; + + // 查找通知特征值(通常具有 notify 或 indicate 属性) + const notifyChar = characteristics.find(c => c.properties.notify || c.properties.indicate); + // 查找写入特征值 + const writeChar = characteristics.find(c => c.properties.write || c.properties.writeNoResponse); + + if (notifyChar) { + this.notifyCharacteristicId = notifyChar.uuid; + console.log('[BLE] 通知特征值:', this.notifyCharacteristicId); + + // 启用通知 + wx.notifyBLECharacteristicValueChange({ + deviceId: deviceId, + serviceId: serviceId, + characteristicId: this.notifyCharacteristicId, + state: true, + success: () => { + console.log('[BLE] 已启用通知'); + if (writeChar) { + this.writeCharacteristicId = writeChar.uuid; + console.log('[BLE] 写入特征值:', this.writeCharacteristicId); + } + this._setState(BLE_STATE.READY); + resolve({ + serviceId: this.serviceId, + notifyCharacteristicId: this.notifyCharacteristicId, + writeCharacteristicId: this.writeCharacteristicId, + }); + }, + fail: (err) => { + console.error('[BLE] 启用通知失败:', err); + reject(new Error('启用通知失败')); + }, + }); + } else { + // 没有通知特征值,但可能有写入特征值 + if (writeChar) { + this.writeCharacteristicId = writeChar.uuid; + console.log('[BLE] 写入特征值:', this.writeCharacteristicId); + } + this._setState(BLE_STATE.READY); + resolve({ + serviceId: this.serviceId, + notifyCharacteristicId: null, + writeCharacteristicId: this.writeCharacteristicId, + }); + } + }, + fail: (err) => { + console.error('[BLE] 获取特征值失败:', err); + reject(new Error('获取特征值失败')); + }, + }); + }); + } + + /** + * 写入数据 + * @param {string} hexData - 十六进制字符串 + * @returns {Promise} + */ + writeCommand(hexData) { + return new Promise((resolve, reject) => { + if (!this.deviceId || !this.serviceId || !this.writeCharacteristicId) { + reject(new Error('设备未连接或无写入特征值')); + return; + } + + const buffer = hex.hexToArrayBuffer(hexData); + + wx.writeBLECharacteristicValue({ + deviceId: this.deviceId, + serviceId: this.serviceId, + characteristicId: this.writeCharacteristicId, + value: buffer, + success: () => { + console.log('[BLE] 写入成功:', hexData); + resolve(); + }, + fail: (err) => { + console.error('[BLE] 写入失败:', err); + reject(new Error('写入失败: ' + (err.errMsg || '未知错误'))); + }, + }); + }); + } + + /** + * 断开连接 + */ + disconnect() { + return new Promise((resolve) => { + if (!this.deviceId) { + resolve(); + return; + } + + wx.closeBLEConnection({ + deviceId: this.deviceId, + success: () => { + console.log('[BLE] 已断开连接'); + }, + complete: () => { + this._setState(BLE_STATE.DISCONNECTED); + this.deviceId = null; + this.serviceId = null; + this.notifyCharacteristicId = null; + this.writeCharacteristicId = null; + resolve(); + }, + }); + }); + } + + /** + * 关闭蓝牙适配器 + */ + closeBluetooth() { + return new Promise((resolve) => { + this.disconnect().then(() => { + wx.closeBluetoothAdapter({ + success: () => { + console.log('[BLE] 已关闭蓝牙适配器'); + }, + complete: () => { + resolve(); + }, + }); + }); + }); + } + + /** + * 获取当前状态 + * @returns {string} + */ + getState() { + return this.state; + } + + /** + * 是否已连接 + * @returns {boolean} + */ + isConnected() { + return this.state === BLE_STATE.READY || this.state === BLE_STATE.CONNECTED; + } + + /** + * 清除数据回调(页面卸载时调用) + */ + clearCallbacks() { + this.onDataCallback = null; + this.onStateChangeCallback = null; + console.log('[BLE] 已清除回调'); + } +} + +// 导出单例 +const bleService = new BleService(); + +module.exports = { + BLE_STATE, + BleService, + bleService, +}; \ No newline at end of file diff --git a/yack-mini/miniprogram/services/mqtt-client.js b/yack-mini/miniprogram/services/mqtt-client.js new file mode 100644 index 00000000..a45e180e --- /dev/null +++ b/yack-mini/miniprogram/services/mqtt-client.js @@ -0,0 +1,317 @@ +/** + * MQTT 客户端服务 + */ +const mqtt = require('../libs/mqtt.min.js'); +const envConfig = require('../env.config.js'); + +// MQTT 状态常量 +const MQTT_STATE = { + DISCONNECTED: 'disconnected', + CONNECTING: 'connecting', + CONNECTED: 'connected', +}; + +class MqttClient { + constructor() { + this.client = null; + this.state = MQTT_STATE.DISCONNECTED; + this.subscriptions = new Map(); + this.onMessageCallback = null; + this.onStateChangeCallback = null; + this.reconnectAttempts = 0; + this.maxReconnectAttempts = 5; + // 当前连接的 clientId(用于检测连接复用) + this.currentClientId = null; + } + + /** + * 设置消息回调 + * @param {Function} callback - (topic, message) => void + */ + onMessage(callback) { + this.onMessageCallback = callback; + } + + /** + * 设置状态变化回调 + * @param {Function} callback - (state) => void + */ + onStateChange(callback) { + this.onStateChangeCallback = callback; + } + + /** + * 更新状态并通知 + * @param {string} state + */ + _setState(state) { + this.state = state; + if (this.onStateChangeCallback) { + this.onStateChangeCallback(state); + } + console.log('[MQTT] 状态变更:', state); + } + + /** + * 连接 MQTT Broker + * @param {Object} options - 连接配置 + * @param {string} options.clientId - 客户端 ID + * @param {string} options.username - 用户名 + * @param {string} options.password - 密码 + * @param {string} options.server - 服务器地址(可选,默认使用 envConfig) + * @returns {Promise} + */ + connect(options) { + return new Promise((resolve, reject) => { + const newClientId = options.clientId; + + // 检查是否需要重建连接(clientId 变化或未连接) + if (this.client && this.client.connected) { + if (this.currentClientId === newClientId) { + // 同一设备,复用连接 + console.log('[MQTT] 已连接, 复用连接, clientId:', newClientId); + resolve(); + return; + } + // 不同设备,先断开旧连接,防止数据串号 + console.log('[MQTT] clientId 变化,断开旧连接:', this.currentClientId, '->', newClientId); + this.client.end(true); // 强制关闭,停止重连 + this.client = null; + this.subscriptions.clear(); + } + + this._setState(MQTT_STATE.CONNECTING); + + const server = options.server || envConfig.mqttServer; + const connectOptions = { + clientId: options.clientId, + username: options.username, + password: options.password, + clean: true, + keepalive: 30, + connectTimeout: 30000, + reconnectPeriod: 5000, + // protocol 从 URL 自动解析,无需手动设置 + }; + + console.log('[MQTT] 正在连接:', server, 'clientId:', options.clientId); + + try { + this.client = mqtt.connect(server, connectOptions); + this.currentClientId = options.clientId; + + // 连接成功 + this.client.on('connect', () => { + console.log('[MQTT] 连接成功'); + this._setState(MQTT_STATE.CONNECTED); + this.reconnectAttempts = 0; + // 重新订阅之前的主题 + this._resubscribe(); + resolve(); + }); + + // 连接错误 + this.client.on('error', (err) => { + console.error('[MQTT] 连接错误:', err); + // 强制关闭客户端,停止重连 + if (this.client) { + this.client.end(true); + this.client = null; + } + this._setState(MQTT_STATE.DISCONNECTED); + reject(new Error('MQTT 连接失败: ' + (err.message || '未知错误'))); + }); + + // 断开连接 + this.client.on('close', () => { + console.log('[MQTT] 连接关闭'); + this._setState(MQTT_STATE.DISCONNECTED); + }); + + // 重连 + this.client.on('reconnect', () => { + this.reconnectAttempts++; + console.log('[MQTT] 正在重连, 尝试次数:', this.reconnectAttempts); + this._setState(MQTT_STATE.CONNECTING); + if (this.reconnectAttempts > this.maxReconnectAttempts) { + console.log('[MQTT] 重连次数超限,停止重连'); + this.disconnect(); + } + }); + + // 收到消息 + this.client.on('message', (topic, message) => { + const msgStr = message.toString(); + console.log('[MQTT] 收到消息, topic:', topic, 'message:', msgStr); + if (this.onMessageCallback) { + this.onMessageCallback(topic, msgStr); + } + // 触发订阅回调 + if (this.subscriptions.has(topic)) { + const callback = this.subscriptions.get(topic); + callback(topic, msgStr); + } + }); + + // 离线 + this.client.on('offline', () => { + console.log('[MQTT] 离线'); + this._setState(MQTT_STATE.DISCONNECTED); + }); + + } catch (err) { + console.error('[MQTT] 创建客户端失败:', err); + this._setState(MQTT_STATE.DISCONNECTED); + reject(new Error('创建 MQTT 客户端失败')); + } + }); + } + + /** + * 发布消息 + * @param {string} topic - 主题 + * @param {string} message - 消息内容 + * @param {Object} options - 发布选项 {qos: 0|1|2, retain: boolean} + * @returns {Promise} + */ + publish(topic, message, options = { qos: 1 }) { + return new Promise((resolve, reject) => { + if (!this.client || !this.client.connected) { + reject(new Error('MQTT 未连接')); + return; + } + + this.client.publish(topic, message, options, (err) => { + if (err) { + console.error('[MQTT] 发布失败:', err); + reject(new Error('发布失败')); + } else { + console.log('[MQTT] 发布成功, topic:', topic, 'message:', message); + resolve(); + } + }); + }); + } + + /** + * 订阅主题 + * @param {string} topic - 主题 + * @param {Function} callback - 消息回调(可选) + * @param {Object} options - 订阅选项 {qos: 0|1|2} + * @returns {Promise} + */ + subscribe(topic, callback = null, options = { qos: 1 }) { + return new Promise((resolve, reject) => { + if (!this.client || !this.client.connected) { + reject(new Error('MQTT 未连接')); + return; + } + + this.client.subscribe(topic, options, (err) => { + if (err) { + console.error('[MQTT] 订阅失败:', err); + reject(new Error('订阅失败')); + } else { + console.log('[MQTT] 订阅成功:', topic); + if (callback) { + this.subscriptions.set(topic, callback); + } + resolve(); + } + }); + }); + } + + /** + * 取消订阅 + * @param {string} topic - 主题 + * @returns {Promise} + */ + unsubscribe(topic) { + return new Promise((resolve, reject) => { + if (!this.client || !this.client.connected) { + resolve(); + return; + } + + this.client.unsubscribe(topic, (err) => { + if (err) { + console.error('[MQTT] 取消订阅失败:', err); + reject(new Error('取消订阅失败')); + } else { + console.log('[MQTT] 取消订阅成功:', topic); + this.subscriptions.delete(topic); + resolve(); + } + }); + }); + } + + /** + * 重新订阅所有主题 + */ + _resubscribe() { + if (this.subscriptions.size === 0) return; + + this.subscriptions.forEach((callback, topic) => { + this.subscribe(topic, callback).catch(err => { + console.error('[MQTT] 重订阅失败:', topic, err); + }); + }); + } + + /** + * 断开连接 + */ + disconnect() { + return new Promise((resolve) => { + if (!this.client) { + resolve(); + return; + } + + this.client.end(false, () => { + console.log('[MQTT] 已断开连接'); + this._setState(MQTT_STATE.DISCONNECTED); + this.client = null; + this.currentClientId = null; + this.subscriptions.clear(); + resolve(); + }); + }); + } + + /** + * 获取当前状态 + * @returns {string} + */ + getState() { + return this.state; + } + + /** + * 是否已连接 + * @returns {boolean} + */ + isConnected() { + return this.client && this.client.connected; + } + + /** + * 清除消息回调(页面卸载时调用) + */ + clearCallbacks() { + this.onMessageCallback = null; + this.onStateChangeCallback = null; + console.log('[MQTT] 已清除回调'); + } +} + +// 导出单例 +const mqttClient = new MqttClient(); + +module.exports = { + MQTT_STATE, + MqttClient, + mqttClient, +}; \ No newline at end of file diff --git a/yack-mini/miniprogram/sitemap.json b/yack-mini/miniprogram/sitemap.json new file mode 100644 index 00000000..27b2b26d --- /dev/null +++ b/yack-mini/miniprogram/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} \ No newline at end of file diff --git a/yack-mini/miniprogram/utils/auth.js b/yack-mini/miniprogram/utils/auth.js new file mode 100644 index 00000000..ddf163af --- /dev/null +++ b/yack-mini/miniprogram/utils/auth.js @@ -0,0 +1,116 @@ +/** + * 认证工具 - 获取 MQTT 凭证 + */ +const storage = require('./storage.js'); + +/** + * 生成 MQTT clientId + * 格式:S&{设备编号}&{产品ID}&{用户ID} + * @param {string} serialNumber - 设备编号 + * @param {number|string} productId - 产品ID + * @param {number|string} userId - 用户ID + * @returns {string} + */ +function generateClientId(serialNumber, productId, userId) { + return `S&${serialNumber}&${productId}&${userId}`; +} + +/** + * 获取 MQTT 连接凭证 + * @param {string} serialNumber - 设备编号 + * @param {number|string} productId - 产品ID + * @param {string} productSecret - 产品密钥 + * @returns {Object|null} { clientId, username, password } + */ +function getMqttCredentials(serialNumber, productId, productSecret) { + const userInfo = storage.getUserInfo(); + if (!userInfo || !userInfo.userId) { + console.error('[Auth] 未登录,无法获取 MQTT 凭证'); + return null; + } + + const userId = userInfo.userId; + const clientId = generateClientId(serialNumber, productId, userId); + + return { + clientId: clientId, + username: serialNumber, + password: productSecret, + }; +} + +/** + * 从设备配置获取 MQTT 凭证 + * @param {Object} device - 设备配置对象 + * @returns {Object|null} + */ +function getMqttCredentialsFromDevice(device) { + if (!device || !device.serialNumber || !device.productId || !device.productSecret) { + console.error('[Auth] 设备配置不完整'); + return null; + } + return getMqttCredentials(device.serialNumber, device.productId, device.productSecret); +} + +/** + * 构建属性上报 Topic + * 格式:/{productId}/{serialNumber}/property/post + * @param {number|string} productId - 产品ID + * @param {string} serialNumber - 设备编号 + * @returns {string} + */ +function buildPropertyPostTopic(productId, serialNumber) { + return `/${productId}/${serialNumber}/property/post`; +} + +/** + * 构建功能下发 Topic + * 格式:/{productId}/{serialNumber}/function/get + * @param {number|string} productId - 产品ID + * @param {string} serialNumber - 设备编号 + * @returns {string} + */ +function buildFunctionGetTopic(productId, serialNumber) { + return `/${productId}/${serialNumber}/function/get`; +} + +/** + * 构建属性读取 Topic + * 格式:/{productId}/{serialNumber}/property/get + * @param {number|string} productId - 产品ID + * @param {string} serialNumber - 设备编号 + * @returns {string} + */ +function buildPropertyGetTopic(productId, serialNumber) { + return `/${productId}/${serialNumber}/property/get`; +} + +/** + * 检查登录状态 + * @returns {boolean} + */ +function checkLoginStatus() { + const token = storage.getToken(); + const userInfo = storage.getUserInfo(); + return !!(token && userInfo && userInfo.userId); +} + +/** + * 获取当前用户ID + * @returns {number|null} + */ +function getCurrentUserId() { + const userInfo = storage.getUserInfo(); + return userInfo ? userInfo.userId : null; +} + +module.exports = { + generateClientId, + getMqttCredentials, + getMqttCredentialsFromDevice, + buildPropertyPostTopic, + buildFunctionGetTopic, + buildPropertyGetTopic, + checkLoginStatus, + getCurrentUserId, +}; \ No newline at end of file diff --git a/yack-mini/miniprogram/utils/hex.js b/yack-mini/miniprogram/utils/hex.js new file mode 100644 index 00000000..2faf3b5b --- /dev/null +++ b/yack-mini/miniprogram/utils/hex.js @@ -0,0 +1,119 @@ +/** + * 十六进制工具函数 + */ + +/** + * ArrayBuffer 转十六进制字符串 + * @param {ArrayBuffer} buffer - ArrayBuffer 数据 + * @returns {string} 十六进制字符串(大写) + */ +function arrayBufferToHex(buffer) { + const hexArr = Array.prototype.map.call( + new Uint8Array(buffer), + function (byte) { + return ('0' + byte.toString(16)).slice(-2); + } + ); + return hexArr.join('').toUpperCase(); +} + +/** + * 十六进制字符串转 ArrayBuffer + * @param {string} hex - 十六进制字符串 + * @returns {ArrayBuffer} ArrayBuffer 数据 + */ +function hexToArrayBuffer(hex) { + // 去除空格和换行 + hex = hex.replace(/\s+/g, ''); + + // 确保长度为偶数 + if (hex.length % 2 !== 0) { + hex = '0' + hex; + } + + const length = hex.length / 2; + const buffer = new ArrayBuffer(length); + const dataView = new DataView(buffer); + + for (let i = 0; i < length; i++) { + const byte = parseInt(hex.substr(i * 2, 2), 16); + dataView.setUint8(i, byte); + } + + return buffer; +} + +/** + * 十六进制字符串转字节数组 + * @param {string} hex - 十六进制字符串 + * @returns {Array} 字节数组 + */ +function hexToBytes(hex) { + hex = hex.replace(/\s+/g, ''); + const bytes = []; + for (let i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.substr(i, 2), 16)); + } + return bytes; +} + +/** + * 字节数组转十六进制字符串 + * @param {Array} bytes - 字节数组 + * @returns {string} 十六进制字符串 + */ +function bytesToHex(bytes) { + return bytes + .map(b => ('0' + b.toString(16)).slice(-2)) + .join('') + .toUpperCase(); +} + +/** + * CRC16 校验(Modbus RTU) + * @param {string} hex - 十六进制字符串(不含 CRC) + * @returns {string} CRC16 校验码(4位十六进制) + */ +function crc16(hex) { + const bytes = hexToBytes(hex); + let crc = 0xFFFF; + + for (let i = 0; i < bytes.length; i++) { + crc ^= bytes[i]; + for (let j = 0; j < 8; j++) { + if (crc & 0x0001) { + crc = (crc >> 1) ^ 0xA001; + } else { + crc = crc >> 1; + } + } + } + + // 返回低位在前,高位在后 + const low = (crc & 0xFF).toString(16).padStart(2, '0'); + const high = ((crc >> 8) & 0xFF).toString(16).padStart(2, '0'); + return (low + high).toUpperCase(); +} + +/** + * 验证 CRC16 校验码 + * @param {string} hex - 完整的十六进制字符串(含 CRC) + * @returns {boolean} 校验是否通过 + */ +function verifyCrc16(hex) { + if (hex.length < 4) return false; + + const data = hex.slice(0, -4); + const crc = hex.slice(-4); + + return crc16(data) === crc; +} + +module.exports = { + arrayBufferToHex, + hexToArrayBuffer, + hexToBytes, + bytesToHex, + crc16, + verifyCrc16, +}; \ No newline at end of file diff --git a/yack-mini/miniprogram/utils/request.js b/yack-mini/miniprogram/utils/request.js new file mode 100644 index 00000000..f188a0a2 --- /dev/null +++ b/yack-mini/miniprogram/utils/request.js @@ -0,0 +1,143 @@ +/** + * 网络请求工具 + * 仅用于登录流程(获取验证码、登录、获取用户信息) + * 业务流程(MQTT 连接、数据上报)不依赖此模块 + */ +const envConfig = require('../env.config.js'); +const storage = require('./storage.js'); + +/** + * 发起 HTTP 请求 + * @param {Object} options - 请求配置 + * @param {string} options.url - 请求地址 + * @param {string} options.method - 请求方法 + * @param {Object} options.data - 请求数据 + * @param {Object} options.header - 请求头 + * @param {boolean} options.auth - 是否需要认证 + * @returns {Promise} + */ +function request(options) { + return new Promise((resolve, reject) => { + const header = options.header || {}; + + // 添加 Token + if (options.auth !== false) { + const token = storage.getToken(); + if (token) { + header['Authorization'] = 'Bearer ' + token; + } + } + + wx.request({ + url: envConfig.baseUrl + options.url, + method: options.method || 'GET', + data: options.data || {}, + header: { + 'Content-Type': 'application/json', + ...header, + }, + success(res) { + if (res.statusCode === 200) { + // yack 统一响应格式 + if (res.data.code === 200) { + resolve(res.data); + } else { + // 业务错误 + const errMsg = res.data.msg || '请求失败'; + console.error('[Request] 业务错误:', errMsg); + reject(new Error(errMsg)); + } + } else if (res.statusCode === 401) { + // Token 过期(仅登录流程使用,业务不依赖 token) + console.error('[Request] 未授权'); + // 不清除登录态,不强制跳转(业务流程独立于 token) + reject(new Error('登录已过期')); + } else { + console.error('[Request] HTTP 错误:', res.statusCode); + reject(new Error('网络请求失败')); + } + }, + fail(err) { + console.error('[Request] 网络错误:', err); + reject(new Error('网络连接失败')); + }, + }); + }); +} + +/** + * GET 请求 + * @param {string} url - 请求地址 + * @param {Object} data - 查询参数 + * @param {Object} options - 其他配置 + */ +function get(url, data = {}, options = {}) { + return request({ + url, + method: 'GET', + data, + ...options, + }); +} + +/** + * POST 请求 + * @param {string} url - 请求地址 + * @param {Object} data - 请求数据 + * @param {Object} options - 其他配置 + */ +function post(url, data = {}, options = {}) { + return request({ + url, + method: 'POST', + data, + ...options, + }); +} + +/** + * 登录接口 + * @param {string} username + * @param {string} password + * @param {string} code - 验证码(可选) + * @param {string} uuid - 验证码 UUID(可选) + */ +function login(username, password, code = '', uuid = '') { + return post('/login', { + username, + password, + code, + uuid, + }, { auth: false }); +} + +/** + * 获取用户信息 + */ +function getInfo() { + return get('/getInfo'); +} + +/** + * 退出登录 + */ +function logout() { + return post('/logout'); +} + +/** + * 获取验证码 + */ +function captchaImage() { + return get('/captchaImage', { showCode: 'false' }); +} + +module.exports = { + request, + get, + post, + login, + getInfo, + logout, + captchaImage, +}; \ No newline at end of file diff --git a/yack-mini/miniprogram/utils/storage.js b/yack-mini/miniprogram/utils/storage.js new file mode 100644 index 00000000..92d22cb8 --- /dev/null +++ b/yack-mini/miniprogram/utils/storage.js @@ -0,0 +1,227 @@ +/** + * 本地存储工具 + */ + +const TOKEN_KEY = 'yack_token'; +const USER_INFO_KEY = 'yack_user_info'; +const DEVICE_LIST_KEY = 'yack_device_list'; +const PENDING_DATA_KEY = 'yack_pending_data'; + +/** + * 获取 Token + * @returns {string|null} + */ +function getToken() { + try { + return wx.getStorageSync(TOKEN_KEY) || null; + } catch (e) { + console.error('[Storage] getToken error:', e); + return null; + } +} + +/** + * 设置 Token + * @param {string} token + */ +function setToken(token) { + try { + wx.setStorageSync(TOKEN_KEY, token); + } catch (e) { + console.error('[Storage] setToken error:', e); + } +} + +/** + * 移除 Token + */ +function removeToken() { + try { + wx.removeStorageSync(TOKEN_KEY); + } catch (e) { + console.error('[Storage] removeToken error:', e); + } +} + +/** + * 获取用户信息 + * @returns {Object|null} + */ +function getUserInfo() { + try { + return wx.getStorageSync(USER_INFO_KEY) || null; + } catch (e) { + console.error('[Storage] getUserInfo error:', e); + return null; + } +} + +/** + * 设置用户信息 + * @param {Object} userInfo + */ +function setUserInfo(userInfo) { + try { + wx.setStorageSync(USER_INFO_KEY, userInfo); + } catch (e) { + console.error('[Storage] setUserInfo error:', e); + } +} + +/** + * 移除用户信息 + */ +function removeUserInfo() { + try { + wx.removeStorageSync(USER_INFO_KEY); + } catch (e) { + console.error('[Storage] removeUserInfo error:', e); + } +} + +/** + * 获取设备列表 + * @returns {Array} + */ +function getDeviceList() { + try { + return wx.getStorageSync(DEVICE_LIST_KEY) || []; + } catch (e) { + console.error('[Storage] getDeviceList error:', e); + return []; + } +} + +/** + * 保存设备列表 + * @param {Array} devices + */ +function saveDeviceList(devices) { + try { + wx.setStorageSync(DEVICE_LIST_KEY, devices); + } catch (e) { + console.error('[Storage] saveDeviceList error:', e); + } +} + +/** + * 添加设备 + * @param {Object} device + */ +function addDevice(device) { + const devices = getDeviceList(); + // 检查是否已存在 + const index = devices.findIndex(d => d.serialNumber === device.serialNumber); + if (index >= 0) { + devices[index] = device; + } else { + devices.push(device); + } + saveDeviceList(devices); +} + +/** + * 删除设备 + * @param {string} serialNumber + */ +function removeDevice(serialNumber) { + const devices = getDeviceList(); + const newDevices = devices.filter(d => d.serialNumber !== serialNumber); + saveDeviceList(newDevices); +} + +/** + * 更新设备 + * @param {Object} device + */ +function updateDevice(device) { + const devices = getDeviceList(); + const index = devices.findIndex(d => d.serialNumber === device.serialNumber); + if (index >= 0) { + devices[index] = device; + saveDeviceList(devices); + } +} + +/** + * 获取单个设备 + * @param {string} serialNumber + * @returns {Object|null} + */ +function getDevice(serialNumber) { + const devices = getDeviceList(); + return devices.find(d => d.serialNumber === serialNumber) || null; +} + +/** + * 缓存待上报数据 + * @param {Object} data + */ +function cachePendingData(data) { + try { + const pendingList = wx.getStorageSync(PENDING_DATA_KEY) || []; + pendingList.push({ + ...data, + cacheTime: Date.now(), + }); + wx.setStorageSync(PENDING_DATA_KEY, pendingList); + console.log('[Storage] 缓存待上报数据:', data); + } catch (e) { + console.error('[Storage] cachePendingData error:', e); + } +} + +/** + * 获取待上报数据 + * @returns {Array} + */ +function getPendingData() { + try { + return wx.getStorageSync(PENDING_DATA_KEY) || []; + } catch (e) { + console.error('[Storage] getPendingData error:', e); + return []; + } +} + +/** + * 清除待上报数据 + */ +function clearPendingData() { + try { + wx.removeStorageSync(PENDING_DATA_KEY); + } catch (e) { + console.error('[Storage] clearPendingData error:', e); + } +} + +/** + * 清除所有数据 + */ +function clearAll() { + try { + wx.clearStorageSync(); + console.log('[Storage] 已清除所有数据'); + } catch (e) { + console.error('[Storage] clearAll error:', e); + } +} + +module.exports = { + getToken, + setToken, + removeToken, + getUserInfo, + setUserInfo, + removeUserInfo, + getDeviceList, + saveDeviceList, + addDevice, + removeDevice, + updateDevice, + getDevice, + cachePendingData, + getPendingData, + clearPendingData, + clearAll, +}; \ No newline at end of file diff --git a/yack-mini/project.config.json b/yack-mini/project.config.json new file mode 100644 index 00000000..5e349b16 --- /dev/null +++ b/yack-mini/project.config.json @@ -0,0 +1,52 @@ +{ + "description": "yack-mini - 岩安测控蓝牙网关微信小程序", + "packOptions": { + "ignore": [], + "include": [] + }, + "setting": { + "bundle": false, + "userConfirmedBundleSwitch": false, + "urlCheck": true, + "scopeDataCheck": false, + "coverView": true, + "es6": true, + "postcss": true, + "compileHotReLoad": false, + "lazyloadPlaceholderEnable": false, + "preloadBackgroundData": false, + "minified": true, + "autoAudits": false, + "newFeature": false, + "uglifyFileName": false, + "uploadWithSourceMap": true, + "useIsolateContext": true, + "nodeModules": false, + "enhance": true, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": true, + "packNpmManually": false, + "enableEngineNative": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "showES6CompileOption": false, + "minifyWXML": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + } + }, + "compileType": "miniprogram", + "libVersion": "3.3.4", + "appid": "wx0000000000000000", + "projectname": "yack-mini", + "condition": {}, + "editorSetting": { + "tabIndent": "insertSpaces", + "tabSize": 2 + }, + "miniprogramRoot": "miniprogram/" +} \ No newline at end of file