feat(controlled): 实现设备激活与安全认证流程

- 添加API客户端、加密存储和provision/token激活逻辑
- WebSocket改用Bearer令牌认证,移除REGISTER请求
- 设备ID改为服务端下发,支持令牌刷新和强制下线处理
- 新增deviceSecret加密存储和accessToken自动刷新
- 更新设备ID获取方式为出厂SN,添加安全存储依赖
This commit is contained in:
2026-08-01 15:13:12 +08:00
parent 376a2c1217
commit 6eb2c7321a
124 changed files with 10535 additions and 862 deletions

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
@@ -6,13 +7,18 @@ import '../models/signal_message.dart';
/// 信令客户端,封装 WebSocket 连接与消息收发。
///
/// 连接成功后自动向服务器发送 REGISTER 注册为 CONTROLLER 设备,
/// 与 Android 端 WebSocketClient 行为一致。
/// 鉴权方式:浏览器/移动端原生 WebSocket 均可在握手阶段通过子协议
/// Sec-WebSocket-Protocol传递 Bearer token
/// WebSocketChannel.connect(uri, protocols: ['signal.v1', 'auth.`<token>`'])
/// 服务端从首子协议取出 auth.`<token>` 进行校验。
///
/// 不再发送 REGISTER —— 连接由服务端根据令牌身份自动完成,并下发 REGISTER_SUCCESS。
class SignalingClient {
final String serverUrl;
final String deviceId;
final String? token;
WebSocketChannel? _channel;
Timer? _heartbeatTimer;
/// 收到信令消息回调(已解析为 SignalMessage
void Function(SignalMessage message)? onMessage;
@@ -26,23 +32,33 @@ class SignalingClient {
/// 连接错误回调。
void Function(String error)? onError;
SignalingClient({required this.serverUrl, required this.deviceId});
/// 令牌失效(关闭码 4001调用方应刷新令牌后重连。
void Function()? onTokenExpired;
/// 建立 WebSocket 连接并注册设备
/// 强制下线(关闭码 4003调用方应停止重连并跳登录
void Function()? onForceLogout;
SignalingClient({required this.serverUrl, this.token});
/// 建立 WebSocket 连接(携带 Bearer 子协议,不再发送 REGISTER
void connect() {
try {
final uri = Uri.parse(serverUrl);
_channel = WebSocketChannel.connect(uri);
final protocols = ['signal.v1'];
if (token != null && token!.isNotEmpty) {
protocols.add('auth.$token');
}
_channel = WebSocketChannel.connect(uri, protocols: protocols);
_channel!.stream.listen(
_onData,
onDone: () => onDisconnected?.call(),
onDone: _onDone,
onError: (Object e) => onError?.call(e.toString()),
cancelOnError: false,
);
// 连接建立后注册设备(与 Android 端 onOpen -> registerDevice 对应)。
_register();
_startHeartbeat();
onConnected?.call();
} catch (e) {
onError?.call(e.toString());
}
@@ -53,22 +69,45 @@ class SignalingClient {
try {
final map = jsonDecode(data) as Map<String, dynamic>;
final message = SignalMessage.fromJson(map);
// 记录服务端下发的本机 deviceIdREGISTER_SUCCESS.fromDeviceId
if (message.type?.toUpperCase() == 'REGISTER_SUCCESS' &&
message.fromDeviceId != null) {
// 透传给上层,由 RemoteController 处理。
}
onMessage?.call(message);
} catch (_) {
// 忽略无法解析的消息。
}
}
void _register() {
final msg = SignalMessage(
type: 'REGISTER',
fromDeviceId: deviceId,
deviceType: 'CONTROLLER',
);
_send(msg);
// 通知上层已“连接”WebSocketChannel 会缓冲发送,直到底层连接就绪);
// 后续 OFFER 等消息会排在 REGISTER 之后发送,保证服务器先完成注册。
onConnected?.call();
void _onDone() {
_stopHeartbeat();
final code = _channel?.closeCode;
if (code == 4001) {
onTokenExpired?.call();
return;
}
if (code == 4003) {
onForceLogout?.call();
return;
}
onDisconnected?.call();
}
void _startHeartbeat() {
_stopHeartbeat();
_heartbeatTimer = Timer.periodic(const Duration(seconds: 25), (_) {
if (_channel != null) {
try {
_channel!.sink.add(jsonEncode({'type': 'PING'}));
} catch (_) {}
}
});
}
void _stopHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = null;
}
/// 发送信令消息。
@@ -80,6 +119,7 @@ class SignalingClient {
/// 关闭连接。
void disconnect() {
_stopHeartbeat();
_channel?.sink.close();
_channel = null;
}