feat: implement P0 security and reliability improvements

- bcrypt password hashing with auto-migration from SHA-256
- BLE command retry (3 attempts, 500ms delay, skip on disconnect)
- BLE auto-reconnect with service re-discovery on disconnect
- Treatment page disconnect/reconnect event handling
- Token refresh endpoint with 3-day grace period
- Client-side token auto-refresh when <24h remaining
- Single treatment record detail API with ownership check
这个提交包含在:
Guoguo
2026-04-28 18:12:30 -07:00
父节点 88adee7743
当前提交 91d5937d8e
修改 10 个文件,包含 307 行新增32 行删除
+106 -6
查看文件
@@ -88,6 +88,8 @@ var _chars = {}
var _cmdSeq = 0
var _pendingAcks = {}
var _listeners = {}
var _autoReconnect = true
var _reconnecting = false
function nextSeq() {
_cmdSeq = (_cmdSeq + 1) % 256
@@ -456,6 +458,28 @@ function writeCommand(type, payload) {
})
}
function writeCommandWithRetry(type, payload, maxRetries) {
if (maxRetries === undefined) maxRetries = 3
var attempt = 0
function tryOnce() {
attempt++
return writeCommand(type, payload).catch(function (err) {
if (err && err.error_code === 0x0C) {
return Promise.reject(err)
}
if (attempt >= maxRetries) {
return Promise.reject(err)
}
return new Promise(function (resolve) {
setTimeout(resolve, 500)
}).then(function () {
return tryOnce()
})
})
}
return tryOnce()
}
function readDeviceInfo() {
return new Promise(function (resolve, reject) {
if (!_connected || !_deviceId || !_chars.deviceInfo) {
@@ -507,20 +531,20 @@ function setParams(options) {
return a.concat(Array.isArray(b) ? b : [b])
}, [])
return writeCommand(CMD.SET_PARAMS, payload)
return writeCommandWithRetry(CMD.SET_PARAMS, payload)
}
function startTreatment(regionMask) {
var mask = regionMask || REGION.FULL_FACE
return writeCommand(CMD.START, [mask])
return writeCommandWithRetry(CMD.START, [mask])
}
function stopTreatment() {
return writeCommand(CMD.STOP, [])
return writeCommandWithRetry(CMD.STOP, [])
}
function queryStatus() {
return writeCommand(CMD.QUERY_STATUS, [])
return writeCommandWithRetry(CMD.QUERY_STATUS, [])
}
function bindDevice(userId, bindToken) {
@@ -530,13 +554,13 @@ function bindDevice(userId, bindToken) {
var tsBytes = uint32ToBytes(ts)
var payload = [0x01].concat(userBytes).concat(tokenBytes).concat(tsBytes)
return writeCommand(CMD.BIND, payload)
return writeCommandWithRetry(CMD.BIND, payload)
}
function unbindDevice(userId) {
var userBytes = hexToBytes(userId)
var payload = [0x02].concat(userBytes)
return writeCommand(CMD.UNBIND, payload)
return writeCommandWithRetry(CMD.UNBIND, payload)
}
function stopScan() {
@@ -545,6 +569,8 @@ function stopScan() {
}
function disconnect() {
_autoReconnect = false
_reconnecting = false
if (_deviceId) {
wx.closeBLEConnection({ deviceId: _deviceId })
_deviceId = null
@@ -556,10 +582,83 @@ function disconnect() {
wx.closeBluetoothAdapter({})
}
function attemptReconnect(deviceId, retriesLeft) {
wx.createBLEConnection({
deviceId: deviceId,
timeout: 10000,
success: function () {
_connected = true
_chars = {}
wx.getBLEDeviceServices({
deviceId: deviceId,
success: function (res) {
var services = res.services
var serviceMap = {}
for (var i = 0; i < services.length; i++) {
var uuid = services[i].uuid.toUpperCase()
if (uuid.indexOf(SERVICE.DEVICE_INFO) !== -1) {
serviceMap.deviceInfo = services[i].uuid
} else if (uuid.indexOf(SERVICE.DATA_COMM) !== -1) {
serviceMap.dataComm = services[i].uuid
}
}
var tasks = []
if (serviceMap.deviceInfo) {
tasks.push(discoverChars(deviceId, serviceMap.deviceInfo, 'deviceInfo'))
}
if (serviceMap.dataComm) {
tasks.push(discoverChars(deviceId, serviceMap.dataComm, 'dataComm'))
}
Promise.all(tasks).then(function () {
var serviceId = serviceMap.dataComm || null
return subscribeToNotifications(deviceId, serviceId)
}).then(function () {
_reconnecting = false
emit('reconnected', { deviceId: deviceId })
})
},
fail: function () {
// Service discovery failed, treat as reconnect failure
if (retriesLeft > 1) {
setTimeout(function () {
attemptReconnect(deviceId, retriesLeft - 1)
}, 2000)
} else {
_reconnecting = false
emit('reconnect_failed', { deviceId: deviceId })
}
}
})
},
fail: function () {
if (retriesLeft > 1) {
setTimeout(function () {
attemptReconnect(deviceId, retriesLeft - 1)
}, 2000)
} else {
_reconnecting = false
emit('reconnect_failed', { deviceId: deviceId })
}
}
})
}
function setAutoReconnect(enabled) {
_autoReconnect = enabled
}
wx.onBLEConnectionStateChange(function (res) {
if (!res.connected) {
_connected = false
emit('disconnected', { deviceId: res.deviceId })
if (_autoReconnect && !_reconnecting && _deviceId) {
_reconnecting = true
setTimeout(function () {
attemptReconnect(_deviceId, 3)
}, 2000)
}
}
})
@@ -605,6 +704,7 @@ module.exports = {
stopScan: stopScan,
connect: connect,
disconnect: disconnect,
setAutoReconnect: setAutoReconnect,
on: on,
off: off,