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
这个提交包含在:
+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,
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户