feat: 增加密码连接
This commit is contained in:
@@ -14,6 +14,8 @@ class RemoteController {
|
||||
final String serverUrl;
|
||||
final String deviceId;
|
||||
final String targetDeviceId;
|
||||
final String? authType;
|
||||
final String? authValue;
|
||||
|
||||
late final SignalingClient _signaling;
|
||||
WebRtcController? _webRtc;
|
||||
@@ -47,10 +49,14 @@ class RemoteController {
|
||||
required this.serverUrl,
|
||||
required this.deviceId,
|
||||
required this.targetDeviceId,
|
||||
this.authType,
|
||||
this.authValue,
|
||||
});
|
||||
|
||||
/// 发起连接:先连接信令服务器,成功后建立 WebRTC 并创建 Offer。
|
||||
void connect() {
|
||||
void connect({String? authType, String? authValue}) {
|
||||
this.authType = authType;
|
||||
this.authValue = authValue;
|
||||
onStatusChanged?.call('状态: 正在连接信令服务器...');
|
||||
|
||||
_signaling = SignalingClient(serverUrl: serverUrl, deviceId: deviceId);
|
||||
@@ -74,6 +80,8 @@ class RemoteController {
|
||||
signaling: _signaling,
|
||||
deviceId: deviceId,
|
||||
targetDeviceId: targetDeviceId,
|
||||
authType: authType,
|
||||
authValue: authValue,
|
||||
);
|
||||
_webRtc!.onConnectionEstablished = () {
|
||||
onConnectionEstablished?.call();
|
||||
@@ -109,13 +117,24 @@ class RemoteController {
|
||||
case 'CONNECTION_REJECTED':
|
||||
case 'REQUEST_ERROR':
|
||||
case 'REQUEST_TIMEOUT':
|
||||
final text = message.payload ?? '连接请求失败';
|
||||
final text = _parseRejectReason(message.payload);
|
||||
onStatusChanged?.call('状态: $text');
|
||||
onConnectionRejected?.call(text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析拒绝原因:优先读取 JSON 中的 reason 字段,否则直接返回原文。
|
||||
String _parseRejectReason(String? payload) {
|
||||
if (payload == null || payload.isEmpty) return '连接请求失败';
|
||||
try {
|
||||
final map = jsonDecode(payload) as Map<String, dynamic>;
|
||||
final r = map['reason'];
|
||||
if (r is String && r.isNotEmpty) return r;
|
||||
} catch (_) {}
|
||||
return payload;
|
||||
}
|
||||
|
||||
void _startStats() {
|
||||
_statsTimer?.cancel();
|
||||
_statsTimer = Timer.periodic(const Duration(seconds: 1), (_) async {
|
||||
|
||||
@@ -99,7 +99,73 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _connect() async {
|
||||
Future<void> _showAuthDialog() async {
|
||||
final serverUrl = _serverUrlController.text.trim();
|
||||
final deviceId = _deviceIdController.text.trim();
|
||||
final target = _targetController.text.trim();
|
||||
if (serverUrl.isEmpty || deviceId.isEmpty || target.isEmpty) {
|
||||
_setStatus('请填写所有字段');
|
||||
return;
|
||||
}
|
||||
|
||||
String selectedType = 'CODE';
|
||||
final valueController = TextEditingController();
|
||||
|
||||
await showCupertinoDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
builder: (ctx, setDialogState) => CupertinoAlertDialog(
|
||||
title: const Text('连接鉴权'),
|
||||
content: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
CupertinoSegmentedControl<String>(
|
||||
children: const {
|
||||
'CODE': Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('动态验证码'),
|
||||
),
|
||||
'PASSWORD': Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('固定密码'),
|
||||
),
|
||||
},
|
||||
groupValue: selectedType,
|
||||
onValueChanged: (v) => setDialogState(() => selectedType = v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CupertinoTextField(
|
||||
controller: valueController,
|
||||
placeholder: '请输入验证码或密码',
|
||||
obscureText: true,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text('取消'),
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text('连接'),
|
||||
onPressed: () {
|
||||
final val = valueController.text.trim();
|
||||
if (val.isEmpty) {
|
||||
_showAlert('请输入验证码或密码');
|
||||
return;
|
||||
}
|
||||
Navigator.of(ctx).pop();
|
||||
_connect(authType: selectedType, authValue: val);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _connect({required String authType, required String authValue}) async {
|
||||
final serverUrl = _serverUrlController.text.trim();
|
||||
final deviceId = _deviceIdController.text.trim();
|
||||
final target = _targetController.text.trim();
|
||||
@@ -115,6 +181,8 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
serverUrl: serverUrl,
|
||||
deviceId: deviceId,
|
||||
targetDeviceId: target,
|
||||
authType: authType,
|
||||
authValue: authValue,
|
||||
);
|
||||
_controller!.onStatusChanged = _setStatus;
|
||||
_controller!.onConnectionEstablished = () {
|
||||
@@ -142,7 +210,7 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
};
|
||||
_controller!.onStats = (stats) => setState(() => _stats = stats);
|
||||
|
||||
_controller!.connect();
|
||||
_controller!.connect(authType: authType, authValue: authValue);
|
||||
}
|
||||
|
||||
void _onRendererUpdate() {
|
||||
@@ -241,7 +309,7 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: CupertinoButton.filled(
|
||||
onPressed: _connect,
|
||||
onPressed: _showAuthDialog,
|
||||
child: const Text('连接被控设备'),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -8,12 +8,16 @@ import 'dart:convert';
|
||||
/// - [toDeviceId] 接收方设备 ID
|
||||
/// - [deviceType] 设备类型:CONTROLLER / CONTROLLED
|
||||
/// - [payload] JSON 字符串形式的负载(SDP / ICE 候选等)
|
||||
/// - [authType] 鉴权类型:CODE(动态验证码)/ PASSWORD(固定密码)
|
||||
/// - [authValue] 鉴权值:动态验证码或固定密码
|
||||
class SignalMessage {
|
||||
final String? type;
|
||||
final String? fromDeviceId;
|
||||
final String? toDeviceId;
|
||||
final String? deviceType;
|
||||
final String? payload;
|
||||
final String? authType;
|
||||
final String? authValue;
|
||||
|
||||
const SignalMessage({
|
||||
this.type,
|
||||
@@ -21,6 +25,8 @@ class SignalMessage {
|
||||
this.toDeviceId,
|
||||
this.deviceType,
|
||||
this.payload,
|
||||
this.authType,
|
||||
this.authValue,
|
||||
});
|
||||
|
||||
factory SignalMessage.fromJson(Map<String, dynamic> json) => SignalMessage(
|
||||
@@ -29,6 +35,8 @@ class SignalMessage {
|
||||
toDeviceId: json['toDeviceId'] as String?,
|
||||
deviceType: json['deviceType'] as String?,
|
||||
payload: json['payload'] as String?,
|
||||
authType: json['authType'] as String?,
|
||||
authValue: json['authValue'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -37,6 +45,8 @@ class SignalMessage {
|
||||
if (toDeviceId != null) 'toDeviceId': toDeviceId,
|
||||
if (deviceType != null) 'deviceType': deviceType,
|
||||
if (payload != null) 'payload': payload,
|
||||
if (authType != null) 'authType': authType,
|
||||
if (authValue != null) 'authValue': authValue,
|
||||
};
|
||||
|
||||
/// 便捷构造方法:payload 为任意 Map,会自动序列化为 JSON 字符串。
|
||||
@@ -46,6 +56,8 @@ class SignalMessage {
|
||||
required String toDeviceId,
|
||||
required String deviceType,
|
||||
required Map<String, dynamic> payload,
|
||||
String? authType,
|
||||
String? authValue,
|
||||
}) {
|
||||
return SignalMessage(
|
||||
type: type,
|
||||
@@ -53,6 +65,8 @@ class SignalMessage {
|
||||
toDeviceId: toDeviceId,
|
||||
deviceType: deviceType,
|
||||
payload: jsonEncode(payload),
|
||||
authType: authType,
|
||||
authValue: authValue,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ class WebRtcController {
|
||||
final SignalingClient signaling;
|
||||
final String deviceId;
|
||||
final String targetDeviceId;
|
||||
final String? authType;
|
||||
final String? authValue;
|
||||
|
||||
RTCPeerConnection? _pc;
|
||||
RTCDataChannel? _dataChannel;
|
||||
@@ -42,6 +44,8 @@ class WebRtcController {
|
||||
required this.signaling,
|
||||
required this.deviceId,
|
||||
required this.targetDeviceId,
|
||||
this.authType,
|
||||
this.authValue,
|
||||
});
|
||||
|
||||
/// 初始化渲染器、PeerConnection,并创建 Offer。
|
||||
@@ -89,7 +93,7 @@ class WebRtcController {
|
||||
};
|
||||
final offer = await _pc!.createOffer(constraints);
|
||||
await _pc!.setLocalDescription(offer);
|
||||
_sendOffer(offer.sdp!);
|
||||
_sendOffer(offer.sdp!, authType: authType, authValue: authValue);
|
||||
}
|
||||
|
||||
void _onTrack(RTCTrackEvent event) {
|
||||
@@ -157,7 +161,7 @@ class WebRtcController {
|
||||
}
|
||||
}
|
||||
|
||||
void _sendOffer(String sdp) {
|
||||
void _sendOffer(String sdp, {String? authType, String? authValue}) {
|
||||
final payload = {'sdp': sdp};
|
||||
final msg = SignalMessage.withPayload(
|
||||
type: 'OFFER',
|
||||
@@ -165,6 +169,8 @@ class WebRtcController {
|
||||
toDeviceId: targetDeviceId,
|
||||
deviceType: 'CONTROLLER',
|
||||
payload: payload,
|
||||
authType: authType,
|
||||
authValue: authValue,
|
||||
);
|
||||
signaling.send(msg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user