diff --git a/Cargo.lock b/Cargo.lock index 26ed9455..3fddb5c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4419,6 +4419,7 @@ dependencies = [ "system_shutdown", "tray-item", "trayicon", + "url", "uuid", "virtual_display", "whoami", @@ -5492,6 +5493,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde 1.0.144", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c950c872..1eb92c4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,9 +64,9 @@ wol-rs = "0.9.1" flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge", optional = true } errno = "0.2.8" rdev = { git = "https://github.com/asur4s/rdev" } +url = { version = "2.1", features = ["serde"] } -[target.'cfg(not(target_os = "linux"))'.dependencies] -reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features=false } +reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] cpal = "0.13.5" diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 570ff6e9..c96dc115 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; +import 'package:flutter_hbb/desktop/widgets/login.dart'; import '../../consts.dart'; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import 'package:get/get.dart'; diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index ad0aa431..e7d6f50e 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -470,141 +470,6 @@ class _DesktopHomePageState extends State } } -/// common login dialog for desktop -/// call this directly -Future loginDialog() async { - String userName = ""; - var userNameMsg = ""; - String pass = ""; - var passMsg = ""; - var userController = TextEditingController(text: userName); - var pwdController = TextEditingController(text: pass); - - var isInProgress = false; - var completer = Completer(); - gFFI.dialogManager.show((setState, close) { - submit() async { - setState(() { - userNameMsg = ""; - passMsg = ""; - isInProgress = true; - }); - cancel() { - setState(() { - isInProgress = false; - }); - } - - userName = userController.text; - pass = pwdController.text; - if (userName.isEmpty) { - userNameMsg = translate("Username missed"); - cancel(); - return; - } - if (pass.isEmpty) { - passMsg = translate("Password missed"); - cancel(); - return; - } - try { - final resp = await gFFI.userModel.login(userName, pass); - if (resp.containsKey('error')) { - passMsg = resp['error']; - cancel(); - return; - } - // {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w, - // token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}} - debugPrint("$resp"); - completer.complete(true); - } catch (err) { - debugPrint(err.toString()); - cancel(); - return; - } - close(); - } - - cancel() { - completer.complete(false); - close(); - } - - return CustomAlertDialog( - title: Text(translate("Login")), - content: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 8.0, - ), - Row( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100), - child: Text( - "${translate('Username')}:", - textAlign: TextAlign.start, - ).marginOnly(bottom: 16.0)), - const SizedBox( - width: 24.0, - ), - Expanded( - child: TextField( - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: userNameMsg.isNotEmpty ? userNameMsg : null), - controller: userController, - focusNode: FocusNode()..requestFocus(), - ), - ), - ], - ), - const SizedBox( - height: 8.0, - ), - Row( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100), - child: Text("${translate('Password')}:") - .marginOnly(bottom: 16.0)), - const SizedBox( - width: 24.0, - ), - Expanded( - child: TextField( - obscureText: true, - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: passMsg.isNotEmpty ? passMsg : null), - controller: pwdController, - ), - ), - ], - ), - const SizedBox( - height: 4.0, - ), - Offstage( - offstage: !isInProgress, child: const LinearProgressIndicator()) - ], - ), - ), - actions: [ - TextButton(onPressed: cancel, child: Text(translate("Cancel"))), - TextButton(onPressed: submit, child: Text(translate("OK"))), - ], - onSubmit: submit, - onCancel: cancel, - ); - }); - return completer.future; -} - void setPasswordDialog() async { final pw = await bind.mainGetPermanentPassword(); final p0 = TextEditingController(text: pw); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index b063c3c1..12bb935e 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -7,6 +7,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; +import 'package:flutter_hbb/desktop/widgets/login.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:get/get.dart'; diff --git a/flutter/lib/desktop/widgets/login.dart b/flutter/lib/desktop/widgets/login.dart new file mode 100644 index 00000000..3e58a6de --- /dev/null +++ b/flutter/lib/desktop/widgets/login.dart @@ -0,0 +1,521 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:get/get.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../common.dart'; + +final kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0); + +class _IconOP extends StatelessWidget { + final String icon; + final double iconWidth; + const _IconOP({Key? key, required this.icon, required this.iconWidth}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 4.0), + child: SvgPicture.asset( + 'assets/$icon.svg', + width: iconWidth, + ), + ); + } +} + +class ButtonOP extends StatelessWidget { + final String op; + final RxString curOP; + final double iconWidth; + final Color primaryColor; + final double height; + final Function() onTap; + + const ButtonOP({ + Key? key, + required this.op, + required this.curOP, + required this.iconWidth, + required this.primaryColor, + required this.height, + required this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row(children: [ + Expanded( + child: Container( + height: height, + padding: kMidButtonPadding, + child: Obx(() => ElevatedButton( + style: ElevatedButton.styleFrom( + primary: curOP.value.isEmpty || curOP.value == op + ? primaryColor + : Colors.grey, + ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), + onPressed: + curOP.value.isEmpty || curOP.value == op ? onTap : null, + child: Stack(children: [ + Center(child: Text('${translate("Continue with")} $op')), + Align( + alignment: Alignment.centerLeft, + child: SizedBox( + width: 120, + child: _IconOP( + icon: op, + iconWidth: iconWidth, + )), + ), + ]), + )), + ), + ) + ]); + } +} + +class ConfigOP { + final String op; + final double iconWidth; + ConfigOP({required this.op, required this.iconWidth}); +} + +class WidgetOP extends StatefulWidget { + final ConfigOP config; + final RxString curOP; + final Function(String) cbLogin; + const WidgetOP({ + Key? key, + required this.config, + required this.curOP, + required this.cbLogin, + }) : super(key: key); + + @override + State createState() { + return _WidgetOPState(); + } +} + +class _WidgetOPState extends State { + Timer? _updateTimer; + String _stateMsg = ''; + String _FailedMsg = ''; + String _url = ''; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _updateTimer?.cancel(); + } + + _beginQueryState() { + _updateTimer = Timer.periodic(Duration(seconds: 1), (timer) { + _updateState(); + }); + } + + _updateState() { + bind.mainAccountAuthResult().then((result) { + if (result.isEmpty) { + return; + } + final resultMap = jsonDecode(result); + if (resultMap == null) { + return; + } + final String stateMsg = resultMap['state_msg']; + String failedMsg = resultMap['failed_msg']; + final String? url = resultMap['url']; + final authBody = resultMap['auth_body']; + if (_stateMsg != stateMsg || _FailedMsg != failedMsg) { + if (_url.isEmpty && url != null && url.isNotEmpty) { + launchUrl(Uri.parse(url)); + _url = url; + } + if (authBody != null) { + _updateTimer?.cancel(); + final String username = authBody['user']['name']; + widget.curOP.value = ''; + widget.cbLogin(username); + } + + setState(() { + _stateMsg = stateMsg; + _FailedMsg = failedMsg; + if (failedMsg.isNotEmpty) { + widget.curOP.value = ''; + _updateTimer?.cancel(); + } + }); + } + }); + } + + _resetState() { + _stateMsg = ''; + _FailedMsg = ''; + _url = ''; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ButtonOP( + op: widget.config.op, + curOP: widget.curOP, + iconWidth: widget.config.iconWidth, + primaryColor: str2color(widget.config.op, 0x7f), + height: 36, + onTap: () async { + _resetState(); + widget.curOP.value = widget.config.op; + await bind.mainAccountAuth(op: widget.config.op); + _beginQueryState(); + }, + ), + Obx(() { + if (widget.curOP.isNotEmpty && + widget.curOP.value != widget.config.op) { + _FailedMsg = ''; + } + return Offstage( + offstage: + _FailedMsg.isEmpty && widget.curOP.value != widget.config.op, + child: Row( + children: [ + Text( + _stateMsg, + style: TextStyle(fontSize: 12), + ), + SizedBox(width: 8), + Text( + _FailedMsg, + style: TextStyle( + fontSize: 14, + color: Colors.red, + ), + ), + ], + )); + }), + Obx( + () => Offstage( + offstage: widget.curOP.value != widget.config.op, + child: const SizedBox( + height: 5.0, + ), + ), + ), + Obx( + () => Offstage( + offstage: widget.curOP.value != widget.config.op, + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: 20), + child: ElevatedButton( + onPressed: () { + widget.curOP.value = ''; + _updateTimer?.cancel(); + _resetState(); + bind.mainAccountAuthCancel(); + }, + child: Text( + translate('Cancel'), + style: TextStyle(fontSize: 15), + ), + ), + ), + ), + ), + ], + ); + } +} + +class LoginWidgetOP extends StatelessWidget { + final List ops; + final RxString curOP; + final Function(String) cbLogin; + + LoginWidgetOP({ + Key? key, + required this.ops, + required this.curOP, + required this.cbLogin, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var children = ops + .map((op) => [ + WidgetOP( + config: op, + curOP: curOP, + cbLogin: cbLogin, + ), + const Divider( + indent: 5, + endIndent: 5, + ) + ]) + .expand((i) => i) + .toList(); + if (children.isNotEmpty) { + children.removeLast(); + } + return SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: children, + )); + } +} + +class LoginWidgetUserPass extends StatelessWidget { + final String username; + final String pass; + final String usernameMsg; + final String passMsg; + final bool isInProgress; + final RxString curOP; + final Function(String, String) onLogin; + const LoginWidgetUserPass({ + Key? key, + required this.username, + required this.pass, + required this.usernameMsg, + required this.passMsg, + required this.isInProgress, + required this.curOP, + required this.onLogin, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var userController = TextEditingController(text: username); + var pwdController = TextEditingController(text: pass); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 8.0, + ), + Container( + padding: kMidButtonPadding, + child: Row( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 100), + child: Text( + '${translate("Username")}:', + textAlign: TextAlign.start, + ).marginOnly(bottom: 16.0)), + const SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: usernameMsg.isNotEmpty ? usernameMsg : null), + controller: userController, + focusNode: FocusNode()..requestFocus(), + ), + ), + ], + ), + ), + const SizedBox( + height: 8.0, + ), + Container( + padding: kMidButtonPadding, + child: Row( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 100), + child: Text('${translate("Password")}:') + .marginOnly(bottom: 16.0)), + const SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + obscureText: true, + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: passMsg.isNotEmpty ? passMsg : null), + controller: pwdController, + ), + ), + ], + ), + ), + const SizedBox( + height: 4.0, + ), + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()), + const SizedBox( + height: 12.0, + ), + Row(children: [ + Expanded( + child: Container( + height: 38, + padding: kMidButtonPadding, + child: Obx(() => ElevatedButton( + style: curOP.value.isEmpty || curOP.value == 'rustdesk' + ? null + : ElevatedButton.styleFrom( + primary: Colors.grey, + ), + child: Text( + translate('Login'), + style: TextStyle(fontSize: 16), + ), + onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk' + ? () { + onLogin(userController.text, pwdController.text); + } + : null, + )), + ), + ), + ]), + ], + ); + } +} + +/// common login dialog for desktop +/// call this directly +Future loginDialog() async { + String username = ''; + var usernameMsg = ''; + String pass = ''; + var passMsg = ''; + var isInProgress = false; + var completer = Completer(); + final RxString curOP = ''.obs; + + gFFI.dialogManager.show((setState, close) { + cancel() { + isInProgress = false; + completer.complete(false); + close(); + } + + onLogin(String username0, String pass0) async { + setState(() { + usernameMsg = ''; + passMsg = ''; + isInProgress = true; + }); + cancel() { + curOP.value = ''; + if (isInProgress) { + setState(() { + isInProgress = false; + }); + } + } + + curOP.value = 'rustdesk'; + username = username0; + pass = pass0; + if (username.isEmpty) { + usernameMsg = translate('Username missed'); + cancel(); + return; + } + if (pass.isEmpty) { + passMsg = translate('Password missed'); + cancel(); + return; + } + try { + final resp = await gFFI.userModel.login(username, pass); + if (resp.containsKey('error')) { + passMsg = resp['error']; + cancel(); + return; + } + // {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w, + // token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}} + debugPrint('$resp'); + completer.complete(true); + } catch (err) { + debugPrint(err.toString()); + cancel(); + return; + } + close(); + } + + return CustomAlertDialog( + title: Text(translate('Login')), + content: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 8.0, + ), + LoginWidgetUserPass( + username: username, + pass: pass, + usernameMsg: usernameMsg, + passMsg: passMsg, + isInProgress: isInProgress, + curOP: curOP, + onLogin: onLogin, + ), + const SizedBox( + height: 8.0, + ), + Center( + child: Text( + translate('or'), + style: TextStyle(fontSize: 16), + )), + const SizedBox( + height: 8.0, + ), + LoginWidgetOP( + ops: [ + ConfigOP(op: 'Github', iconWidth: 20), + ConfigOP(op: 'Google', iconWidth: 20), + ConfigOP(op: 'Okta', iconWidth: 38), + ], + curOP: curOP, + cbLogin: (String username) { + gFFI.userModel.userName.value = username; + completer.complete(true); + close(); + }, + ), + ], + ), + ), + actions: [msgBoxButton(translate('Close'), cancel)], + onCancel: cancel, + ); + }); + return completer.future; +} diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 7a82bcdd..269439b1 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -291,12 +291,12 @@ class _SettingsState extends State with WidgetsBindingObserver { return SettingsList( sections: [ SettingsSection( - title: Text(translate("Account")), + title: Text(translate('Account')), tiles: [ SettingsTile.navigation( title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty - ? translate("Login") - : '${translate("Logout")} (${gFFI.userModel.userName.value})')), + ? translate('Login') + : '${translate('Logout')} (${gFFI.userModel.userName.value})')), leading: Icon(Icons.person), onPressed: (context) { if (gFFI.userModel.userName.value.isEmpty) { diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index 721aac5b..d2e83990 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; @@ -9,7 +10,7 @@ import 'model.dart'; import 'platform_model.dart'; class UserModel { - var userName = "".obs; + var userName = ''.obs; WeakReference parent; UserModel(this.parent) { @@ -18,7 +19,7 @@ class UserModel { void refreshCurrentUser() async { await getUserName(); - final token = await bind.mainGetLocalOption(key: "access_token"); + final token = await bind.mainGetLocalOption(key: 'access_token'); if (token == '') return; final url = await bind.mainGetApiServer(); final body = { @@ -28,8 +29,8 @@ class UserModel { try { final response = await http.post(Uri.parse('$url/api/currentUser'), headers: { - "Content-Type": "application/json", - "Authorization": "Bearer $token" + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token' }, body: json.encode(body)); final status = response.statusCode; @@ -44,9 +45,9 @@ class UserModel { } void resetToken() async { - await bind.mainSetLocalOption(key: "access_token", value: ""); - await bind.mainSetLocalOption(key: "user_info", value: ""); - userName.value = ""; + await bind.mainSetLocalOption(key: 'access_token', value: ''); + await bind.mainSetLocalOption(key: 'user_info', value: ''); + userName.value = ''; } Future _parseResp(String body) async { @@ -57,13 +58,13 @@ class UserModel { } final token = data['access_token']; if (token != null) { - await bind.mainSetLocalOption(key: "access_token", value: token); + await bind.mainSetLocalOption(key: 'access_token', value: token); } final info = data['user']; if (info != null) { final value = json.encode(info); - await bind.mainSetOption(key: "user_info", value: value); - userName.value = info["name"]; + await bind.mainSetOption(key: 'user_info', value: value); + userName.value = info['name']; } return ''; } @@ -74,10 +75,12 @@ class UserModel { } final userInfo = await bind.mainGetLocalOption(key: 'user_info'); if (userInfo.trim().isEmpty) { - return ""; + return ''; } final m = jsonDecode(userInfo); - if (m != null) { + if (m == null) { + userName.value = ''; + } else { userName.value = m['name'] ?? ''; } return userName.value; @@ -86,10 +89,10 @@ class UserModel { Future logOut() async { final tag = gFFI.dialogManager.showLoading(translate('Waiting')); final url = await bind.mainGetApiServer(); - final _ = await http.post(Uri.parse("$url/api/logout"), + final _ = await http.post(Uri.parse('$url/api/logout'), body: { - "id": await bind.mainGetMyId(), - "uuid": await bind.mainGetUuid(), + 'id': await bind.mainGetMyId(), + 'uuid': await bind.mainGetUuid(), }, headers: await getHttpHeaders()); await Future.wait([ @@ -98,30 +101,30 @@ class UserModel { bind.mainSetLocalOption(key: 'selected-tags', value: ''), ]); parent.target?.abModel.clear(); - userName.value = ""; + userName.value = ''; gFFI.dialogManager.dismissByTag(tag); } Future> login(String userName, String pass) async { final url = await bind.mainGetApiServer(); try { - final resp = await http.post(Uri.parse("$url/api/login"), - headers: {"Content-Type": "application/json"}, + final resp = await http.post(Uri.parse('$url/api/login'), + headers: {'Content-Type': 'application/json'}, body: jsonEncode({ - "username": userName, - "password": pass, - "id": await bind.mainGetMyId(), - "uuid": await bind.mainGetUuid() + 'username': userName, + 'password': pass, + 'id': await bind.mainGetMyId(), + 'uuid': await bind.mainGetUuid() })); final body = jsonDecode(resp.body); bind.mainSetLocalOption( - key: "access_token", value: body['access_token'] ?? ""); + key: 'access_token', value: body['access_token'] ?? ''); bind.mainSetLocalOption( - key: "user_info", value: jsonEncode(body['user'])); - this.userName.value = body['user']?['name'] ?? ""; + key: 'user_info', value: jsonEncode(body['user'])); + this.userName.value = body['user']?['name'] ?? ''; return body; } catch (err) { - return {"error": "$err"}; + return {'error': '$err'}; } } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index f20eb615..9552bd36 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -13,6 +13,8 @@ use hbb_common::{ fs, log, }; +// use crate::hbbs_http::account::AuthResult; + use crate::flutter::{self, SESSIONS}; #[cfg(target_os = "android")] use crate::start_server; @@ -1082,6 +1084,20 @@ pub fn install_install_path() -> SyncReturn { SyncReturn(install_path()) } +pub fn main_account_auth(op: String) { + let id = get_id(); + let uuid = get_uuid(); + account_auth(op, id, uuid); +} + +pub fn main_account_auth_cancel() { + account_auth_cancel() +} + +pub fn main_account_auth_result() -> String { + account_auth_result() +} + #[cfg(target_os = "android")] pub mod server_side { use jni::{ diff --git a/src/hbbs_http.rs b/src/hbbs_http.rs new file mode 100644 index 00000000..4360b6e8 --- /dev/null +++ b/src/hbbs_http.rs @@ -0,0 +1,38 @@ +use hbb_common::{ + anyhow::{self, bail}, + tokio, ResultType, +}; +use reqwest::blocking::Response; +use serde::de::DeserializeOwned; +use serde_derive::Deserialize; +use serde_json::{Map, Value}; + +pub mod account; + +#[derive(Debug)] +pub enum HbbHttpResponse { + ErrorFormat, + Error(String), + DataTypeFormat, + Data(T), +} + +impl TryFrom for HbbHttpResponse { + type Error = reqwest::Error; + + fn try_from(resp: Response) -> Result>::Error> { + let map = resp.json::>()?; + if let Some(error) = map.get("error") { + if let Some(err) = error.as_str() { + Ok(Self::Error(err.to_owned())) + } else { + Ok(Self::ErrorFormat) + } + } else { + match serde_json::from_value(Value::Object(map)) { + Ok(v) => Ok(Self::Data(v)), + Err(_) => Ok(Self::DataTypeFormat), + } + } + } +} diff --git a/src/hbbs_http/account.rs b/src/hbbs_http/account.rs new file mode 100644 index 00000000..cdf72497 --- /dev/null +++ b/src/hbbs_http/account.rs @@ -0,0 +1,255 @@ +use super::HbbHttpResponse; +use hbb_common::{ + config::{Config, LocalConfig}, + log, sleep, tokio, ResultType, +}; +use reqwest::blocking::Client; +use serde_derive::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, + time::{Duration, Instant}, +}; +use url::Url; + +lazy_static::lazy_static! { + static ref API_SERVER: String = crate::get_api_server( + Config::get_option("api-server"), Config::get_option("custom-rendezvous-server")); + static ref OIDC_SESSION: Arc> = Arc::new(RwLock::new(OidcSession::new())); +} + +const QUERY_INTERVAL_SECS: f32 = 1.0; +const QUERY_TIMEOUT_SECS: u64 = 60 * 3; +const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth"; +const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth"; +const LOGIN_ACCOUNT_AUTH: &str = "Login account auth"; + +#[derive(Deserialize, Clone, Debug)] +pub struct OidcAuthUrl { + code: String, + url: Url, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct UserPayload { + pub id: String, + pub name: String, + pub email: Option, + pub note: Option, + pub status: Option, + pub grp: Option, + pub is_admin: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthBody { + pub access_token: String, + pub token_type: String, + pub user: UserPayload, +} + +pub struct OidcSession { + client: Client, + state_msg: &'static str, + failed_msg: String, + code_url: Option, + auth_body: Option, + keep_querying: bool, + running: bool, + query_timeout: Duration, +} + +#[derive(Serialize)] +pub struct AuthResult { + pub state_msg: String, + pub failed_msg: String, + pub url: Option, + pub auth_body: Option, +} + +impl OidcSession { + fn new() -> Self { + Self { + client: Client::new(), + state_msg: REQUESTING_ACCOUNT_AUTH, + failed_msg: "".to_owned(), + code_url: None, + auth_body: None, + keep_querying: false, + running: false, + query_timeout: Duration::from_secs(QUERY_TIMEOUT_SECS), + } + } + + fn auth(op: &str, id: &str, uuid: &str) -> ResultType> { + Ok(OIDC_SESSION + .read() + .unwrap() + .client + .post(format!("{}/api/oidc/auth", *API_SERVER)) + .json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)])) + .send()? + .try_into()?) + } + + fn query(code: &str, id: &str, uuid: &str) -> ResultType> { + let url = reqwest::Url::parse_with_params( + &format!("{}/api/oidc/auth-query", *API_SERVER), + &[("code", code), ("id", id), ("uuid", uuid)], + )?; + Ok(OIDC_SESSION + .read() + .unwrap() + .client + .get(url) + .send()? + .try_into()?) + } + + fn reset(&mut self) { + self.state_msg = REQUESTING_ACCOUNT_AUTH; + self.failed_msg = "".to_owned(); + self.keep_querying = true; + self.running = false; + self.code_url = None; + self.auth_body = None; + } + + fn before_task(&mut self) { + self.reset(); + self.running = true; + } + + fn after_task(&mut self) { + self.running = false; + } + + fn sleep(secs: f32) { + std::thread::sleep(std::time::Duration::from_secs_f32(secs)); + } + + fn auth_task(op: String, id: String, uuid: String) { + let auth_request_res = Self::auth(&op, &id, &uuid); + log::info!("Request oidc auth result: {:?}", &auth_request_res); + let code_url = match auth_request_res { + Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url, + Ok(HbbHttpResponse::<_>::Error(err)) => { + OIDC_SESSION + .write() + .unwrap() + .set_state(REQUESTING_ACCOUNT_AUTH, err); + return; + } + Ok(_) => { + OIDC_SESSION + .write() + .unwrap() + .set_state(REQUESTING_ACCOUNT_AUTH, "Invalid auth response".to_owned()); + return; + } + Err(err) => { + OIDC_SESSION + .write() + .unwrap() + .set_state(REQUESTING_ACCOUNT_AUTH, err.to_string()); + return; + } + }; + + OIDC_SESSION + .write() + .unwrap() + .set_state(WAITING_ACCOUNT_AUTH, "".to_owned()); + OIDC_SESSION.write().unwrap().code_url = Some(code_url.clone()); + + let begin = Instant::now(); + let query_timeout = OIDC_SESSION.read().unwrap().query_timeout; + while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < query_timeout { + match Self::query(&code_url.code, &id, &uuid) { + Ok(HbbHttpResponse::<_>::Data(auth_body)) => { + LocalConfig::set_option( + "access_token".to_owned(), + auth_body.access_token.clone(), + ); + LocalConfig::set_option( + "user_info".to_owned(), + serde_json::to_string(&auth_body.user).unwrap_or_default(), + ); + OIDC_SESSION + .write() + .unwrap() + .set_state(LOGIN_ACCOUNT_AUTH, "".to_owned()); + OIDC_SESSION.write().unwrap().auth_body = Some(auth_body); + return; + } + Ok(HbbHttpResponse::<_>::Error(err)) => { + if err.contains("No authed oidc is found") { + // ignore, keep querying + } else { + OIDC_SESSION + .write() + .unwrap() + .set_state(WAITING_ACCOUNT_AUTH, err); + return; + } + } + Ok(_) => { + // ignore + } + Err(err) => { + log::trace!("Failed query oidc {}", err); + // ignore + } + } + Self::sleep(QUERY_INTERVAL_SECS); + } + + if begin.elapsed() >= query_timeout { + OIDC_SESSION + .write() + .unwrap() + .set_state(WAITING_ACCOUNT_AUTH, "timeout".to_owned()); + } + + // no need to handle "keep_querying == false" + } + + fn set_state(&mut self, state_msg: &'static str, failed_msg: String) { + self.state_msg = state_msg; + self.failed_msg = failed_msg; + } + + fn wait_stop_querying() { + let wait_secs = 0.3; + while OIDC_SESSION.read().unwrap().running { + Self::sleep(wait_secs); + } + } + + pub fn account_auth(op: String, id: String, uuid: String) { + Self::auth_cancel(); + Self::wait_stop_querying(); + OIDC_SESSION.write().unwrap().before_task(); + std::thread::spawn(|| { + Self::auth_task(op, id, uuid); + OIDC_SESSION.write().unwrap().after_task(); + }); + } + + fn get_result_(&self) -> AuthResult { + AuthResult { + state_msg: self.state_msg.to_string(), + failed_msg: self.failed_msg.clone(), + url: self.code_url.as_ref().map(|x| x.url.to_string()), + auth_body: self.auth_body.clone(), + } + } + + pub fn auth_cancel() { + OIDC_SESSION.write().unwrap().keep_querying = false; + } + + pub fn get_result() -> AuthResult { + OIDC_SESSION.read().unwrap().get_result_() + } +} diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 3ee4735e..7240f2a9 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "请选择要分享的画面(对端操作)。"), ("Show RustDesk", "显示rustdesk"), ("This PC", "此电脑"), + ("or", "或"), + ("Continue with", "使用"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index e0998a7b..b51cb69e 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protějšku)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 9aa4f00b..c4d633b9 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Vælg venligst den skærm, der skal deles (Betjen på peer-siden)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 4ce6cbd5..9eb90ebc 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 72aa4585..3415fa46 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -35,5 +35,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."), ("JumpLink", "View"), ("Stop service", "Stop Service"), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 8fb58cf8..e7a35d93 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Bonvolu Elekti la ekranon por esti dividita (Funkciu ĉe la sama flanko)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index fa6aba29..4064d0fd 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Seleccione la pantalla que se compartirá (Operar en el lado del par)."), ("Show RustDesk", "Mostrar RustDesk"), ("This PC", "Este PC"), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index f1119e16..a64fd602 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 262574c4..c449c393 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt (a társoldalon működjön)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 2d2ab9b1..6f328f12 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan (Operasi di sisi rekan)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 6965e661..75e7859e 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 12dc9eba..0e693137 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(ピア側で操作)。"), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 406de6ce..601db354 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하십시오(피어 측에서 작동)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index df1237bf..359e14f5 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Бөлісетін экранды таңдаңыз (бірдей жағынан жұмыс жасаңыз)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 201a6081..382b254f 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index a9189fc1..b99cb9db 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Por favor, selecione a tela a ser compartilhada (operar no lado do peer)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index d6a7ccec..60050697 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", ""), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index dfe1f7e8..4eae0d15 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."), ("Show RustDesk", "Показать RustDesk"), ("This PC", "Этот компьютер"), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 38196c01..4dfd8b02 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Vyberte obrazovku, ktorú chcete zdieľať (Ovládajte na strane partnera)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index d5130d66..7c1f18df 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", ""), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index fe9b5d2f..f856182f 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Lütfen paylaşılacak ekranı seçiniz (Ekran tarafında çalıştırın)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 0f986ffa..6a196feb 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 1c34d082..95d19b26 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Будь ласка, виберіть екран, до якого потрібно надати доступ (працюйте на стороні однорангового пристрою)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 255b60de..c95498ca 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please Select the screen to be shared(Operate on the peer side).", "Vui lòng Chọn màn hình để chia sẻ (Hoạt động ở phía ngang hàng)."), ("Show RustDesk", ""), ("This PC", ""), + ("or", ""), + ("Continue with", ""), ].iter().cloned().collect(); } diff --git a/src/lib.rs b/src/lib.rs index 58dc50b0..eb8a876e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,8 @@ mod ui_cm_interface; mod ui_interface; mod ui_session_interface; +mod hbbs_http; + #[cfg(windows)] pub mod clipboard_file; diff --git a/src/main.rs b/src/main.rs index ac8fd521..9c717030 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,8 +32,8 @@ fn main() { if !common::global_init() { return; } - use hbb_common::log; use clap::App; + use hbb_common::log; let args = format!( "-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]' -k, --key=[KEY] '' @@ -45,7 +45,7 @@ fn main() { .about("RustDesk command line tool") .args_from_usage(&args) .get_matches(); - use hbb_common::{env_logger::*, config::LocalConfig}; + use hbb_common::{config::LocalConfig, env_logger::*}; init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); if let Some(p) = matches.value_of("port-forward") { let options: Vec = p.split(":").map(|x| x.to_owned()).collect(); @@ -73,7 +73,14 @@ fn main() { } let key = matches.value_of("key").unwrap_or("").to_owned(); let token = LocalConfig::get_option("access_token"); - cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token); + cli::start_one_port_forward( + options[0].clone(), + port, + remote_host, + remote_port, + key, + token, + ); } common::global_clean(); -} \ No newline at end of file +} diff --git a/src/server.rs b/src/server.rs index 58aab8fd..04814db4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,5 @@ use crate::ipc::Data; +use bytes::Bytes; pub use connection::*; use hbb_common::{ allow_err, @@ -20,7 +21,6 @@ use std::{ sync::{Arc, Mutex, RwLock, Weak}, time::Duration, }; -use bytes::Bytes; pub mod audio_service; cfg_if::cfg_if! { @@ -140,7 +140,8 @@ pub async fn create_tcp_connection( .write_to_bytes() .unwrap_or_default(), &sk, - ).into(), + ) + .into(), ..Default::default() }); timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??; @@ -310,9 +311,9 @@ pub fn check_zombie() { } /// Start the host server that allows the remote peer to control the current machine. -/// +/// /// # Arguments -/// +/// /// * `is_server` - Whether the current client is definitely the server. /// If true, the server will be started. /// Otherwise, client will check if there's already a server and start one if not. @@ -323,9 +324,9 @@ pub async fn start_server(is_server: bool) { } /// Start the host server that allows the remote peer to control the current machine. -/// +/// /// # Arguments -/// +/// /// * `is_server` - Whether the current client is definitely the server. /// If true, the server will be started. /// Otherwise, client will check if there's already a server and start one if not. diff --git a/src/ui_interface.rs b/src/ui_interface.rs index cb2c178d..04ba90cb 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -20,8 +20,7 @@ use hbb_common::{ tokio::{self, sync::mpsc, time}, }; -use crate::ipc; -use crate::{common::SOFTWARE_UPDATE_URL, platform}; +use crate::{common::SOFTWARE_UPDATE_URL, hbbs_http::account, ipc, platform}; type Message = RendezvousMessage; @@ -843,6 +842,18 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender String { + serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default() +} + // notice: avoiding create ipc connecton repeatly, // because windows named pipe has serious memory leak issue. #[tokio::main(flavor = "current_thread")]