Files
VibeCoding/webrtc_controller_flutter/lib/signaling/signaling_client.dart
tongtongstudio 6eb2c7321a feat(controlled): 实现设备激活与安全认证流程
- 添加API客户端、加密存储和provision/token激活逻辑
- WebSocket改用Bearer令牌认证,移除REGISTER请求
- 设备ID改为服务端下发,支持令牌刷新和强制下线处理
- 新增deviceSecret加密存储和accessToken自动刷新
- 更新设备ID获取方式为出厂SN,添加安全存储依赖
2026-08-01 15:13:12 +08:00

129 lines
3.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../models/signal_message.dart';
/// 信令客户端,封装 WebSocket 连接与消息收发。
///
/// 鉴权方式:浏览器/移动端原生 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? token;
WebSocketChannel? _channel;
Timer? _heartbeatTimer;
/// 收到信令消息回调(已解析为 SignalMessage
void Function(SignalMessage message)? onMessage;
/// 连接成功回调。
void Function()? onConnected;
/// 连接断开回调。
void Function()? onDisconnected;
/// 连接错误回调。
void Function(String error)? onError;
/// 令牌失效(关闭码 4001调用方应刷新令牌后重连。
void Function()? onTokenExpired;
/// 强制下线(关闭码 4003调用方应停止重连并跳登录。
void Function()? onForceLogout;
SignalingClient({required this.serverUrl, this.token});
/// 建立 WebSocket 连接(携带 Bearer 子协议,不再发送 REGISTER
void connect() {
try {
final uri = Uri.parse(serverUrl);
final protocols = ['signal.v1'];
if (token != null && token!.isNotEmpty) {
protocols.add('auth.$token');
}
_channel = WebSocketChannel.connect(uri, protocols: protocols);
_channel!.stream.listen(
_onData,
onDone: _onDone,
onError: (Object e) => onError?.call(e.toString()),
cancelOnError: false,
);
_startHeartbeat();
onConnected?.call();
} catch (e) {
onError?.call(e.toString());
}
}
void _onData(dynamic data) {
if (data is! String) return;
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 _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;
}
/// 发送信令消息。
void send(SignalMessage message) => _send(message);
void _send(SignalMessage message) {
_channel?.sink.add(jsonEncode(message.toJson()));
}
/// 关闭连接。
void disconnect() {
_stopHeartbeat();
_channel?.sink.close();
_channel = null;
}
bool get isConnected => _channel != null;
}