diff --git a/Cargo.lock b/Cargo.lock index 26ed9455f..3fddb5c37 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 c950c8723..1eb92c4e4 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 570ff6e95..c96dc115a 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 ad0aa4316..e7d6f50e8 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 b063c3c15..12bb935e9 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 000000000..3e58a6de2 --- /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 7a82bcdd8..269439b1d 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 721aac5b5..d2e83990b 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 f20eb6153..9552bd36e 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 000000000..4360b6e8c --- /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 000000000..cdf724971 --- /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 3ee4735e3..7240f2a91 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 e0998a7bb..b51cb69e9 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 9aa4f00b9..c4d633b9b 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 4ce6cbd56..9eb90ebcd 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 72aa45853..3415fa463 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 8fb58cf83..e7a35d937 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 fa6aba297..4064d0fd7 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 f1119e166..a64fd6028 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 262574c43..c449c393d 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 2d2ab9b1a..6f328f127 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 6965e6610..75e7859ed 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 12dc9ebaf..0e6931379 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 406de6cef..601db354d 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 df1237bfb..359e14f55 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 201a60811..382b254f0 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 a9189fc14..b99cb9db0 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 d6a7ccec0..600506979 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 dfe1f7e8f..4eae0d153 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 38196c011..4dfd8b02e 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 d5130d66b..7c1f18df3 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 fe9b5d2fc..f856182f3 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 0f986ffa1..6a196feb7 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 1c34d0825..95d19b26b 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 255b60def..c95498ca8 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 58dc50b04..eb8a876ec 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 ac8fd5219..9c7170309 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 58aab8fd1..04814db42 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 cb2c178d6..04ba90cb2 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")]