mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-12-13 19:47:17 +00:00
@@ -2,15 +2,12 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.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 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../widgets/button.dart';
|
||||
|
||||
class _IconOP extends StatelessWidget {
|
||||
final String icon;
|
||||
@@ -92,10 +89,12 @@ class ConfigOP {
|
||||
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
|
||||
@@ -107,9 +106,8 @@ class WidgetOP extends StatefulWidget {
|
||||
class _WidgetOPState extends State<WidgetOP> {
|
||||
Timer? _updateTimer;
|
||||
String _stateMsg = '';
|
||||
String _stateFailedMsg = '';
|
||||
String _FailedMsg = '';
|
||||
String _url = '';
|
||||
String _username = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -138,17 +136,28 @@ class _WidgetOPState extends State<WidgetOP> {
|
||||
return;
|
||||
}
|
||||
final String stateMsg = resultMap['state_msg'];
|
||||
final String failedMsg = resultMap['failed_msg'];
|
||||
// to-do: test null url
|
||||
final String url = resultMap['url'];
|
||||
if (_stateMsg != stateMsg) {
|
||||
if (_url.isEmpty && url.isNotEmpty) {
|
||||
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;
|
||||
_stateFailedMsg = failedMsg;
|
||||
_FailedMsg = failedMsg;
|
||||
if (failedMsg.isNotEmpty) {
|
||||
widget.curOP.value = '';
|
||||
_updateTimer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -156,74 +165,95 @@ class _WidgetOPState extends State<WidgetOP> {
|
||||
|
||||
_resetState() {
|
||||
_stateMsg = '';
|
||||
_stateFailedMsg = '';
|
||||
_FailedMsg = '';
|
||||
_url = '';
|
||||
_username = '';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 500),
|
||||
child: Column(
|
||||
children: [
|
||||
ButtonOP(
|
||||
op: widget.config.op,
|
||||
curOP: widget.curOP,
|
||||
iconWidth: widget.config.iconWidth,
|
||||
primaryColor: str2color(widget.config.op, 0x7f),
|
||||
height: 40,
|
||||
onTap: () {
|
||||
widget.curOP.value = widget.config.op;
|
||||
bind.mainAccountAuth(op: widget.config.op);
|
||||
_beginQueryState();
|
||||
},
|
||||
),
|
||||
Obx(() => Offstage(
|
||||
offstage: widget.curOP.value != widget.config.op,
|
||||
child: Text(
|
||||
_stateMsg,
|
||||
style: TextStyle(fontSize: 12),
|
||||
))),
|
||||
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();
|
||||
},
|
||||
child: Text(
|
||||
translate('Cancel'),
|
||||
style: TextStyle(fontSize: 15),
|
||||
return Column(
|
||||
children: [
|
||||
ButtonOP(
|
||||
op: widget.config.op,
|
||||
curOP: widget.curOP,
|
||||
iconWidth: widget.config.iconWidth,
|
||||
primaryColor: str2color(widget.config.op, 0x7f),
|
||||
height: 40,
|
||||
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<ConfigOP> ops;
|
||||
final RxString curOP = ''.obs;
|
||||
final RxString curOP;
|
||||
final Function(String) cbLogin;
|
||||
|
||||
LoginWidgetOP({
|
||||
Key? key,
|
||||
required this.ops,
|
||||
required this.curOP,
|
||||
required this.cbLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -233,6 +263,7 @@ class LoginWidgetOP extends StatelessWidget {
|
||||
WidgetOP(
|
||||
config: op,
|
||||
curOP: curOP,
|
||||
cbLogin: cbLogin,
|
||||
),
|
||||
const Divider()
|
||||
])
|
||||
@@ -256,6 +287,7 @@ class LoginWidgetUserPass extends StatelessWidget {
|
||||
final String usernameMsg;
|
||||
final String passMsg;
|
||||
final bool isInProgress;
|
||||
final RxString curOP;
|
||||
final Function(String, String) onLogin;
|
||||
const LoginWidgetUserPass({
|
||||
Key? key,
|
||||
@@ -264,6 +296,7 @@ class LoginWidgetUserPass extends StatelessWidget {
|
||||
required this.usernameMsg,
|
||||
required this.passMsg,
|
||||
required this.isInProgress,
|
||||
required this.curOP,
|
||||
required this.onLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -271,86 +304,90 @@ class LoginWidgetUserPass extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
var userController = TextEditingController(text: username);
|
||||
var pwdController = TextEditingController(text: pass);
|
||||
return 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()),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Row(children: [
|
||||
return 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: Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||
child: ElevatedButton(
|
||||
child: const Text(
|
||||
'Login',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
onPressed: () {
|
||||
onLogin(userController.text, pwdController.text);
|
||||
},
|
||||
),
|
||||
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()),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||
child: Obx(() => ElevatedButton(
|
||||
style: curOP.value.isEmpty || curOP.value == 'rustdesk'
|
||||
? null
|
||||
: ElevatedButton.styleFrom(
|
||||
primary: Colors.grey,
|
||||
),
|
||||
child: const Text(
|
||||
'Login',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk'
|
||||
? () {
|
||||
onLogin(userController.text, pwdController.text);
|
||||
}
|
||||
: null,
|
||||
)),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -364,6 +401,7 @@ Future<bool> loginDialog() async {
|
||||
var passMsg = '';
|
||||
var isInProgress = false;
|
||||
var completer = Completer<bool>();
|
||||
final RxString curOP = ''.obs;
|
||||
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
cancel() {
|
||||
@@ -379,6 +417,7 @@ Future<bool> loginDialog() async {
|
||||
isInProgress = true;
|
||||
});
|
||||
cancel() {
|
||||
curOP.value = '';
|
||||
if (isInProgress) {
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
@@ -386,17 +425,16 @@ Future<bool> loginDialog() async {
|
||||
}
|
||||
}
|
||||
|
||||
curOP.value = 'rustdesk';
|
||||
username = username0;
|
||||
pass = pass0;
|
||||
if (username.isEmpty) {
|
||||
usernameMsg = translate('Username missed');
|
||||
debugPrint('REMOVE ME ====================== username empty');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (pass.isEmpty) {
|
||||
passMsg = translate('Password missed');
|
||||
debugPrint('REMOVE ME ====================== password empty');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
@@ -404,7 +442,6 @@ Future<bool> loginDialog() async {
|
||||
final resp = await gFFI.userModel.login(username, pass);
|
||||
if (resp.containsKey('error')) {
|
||||
passMsg = resp['error'];
|
||||
debugPrint('REMOVE ME ====================== password error');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
@@ -414,8 +451,6 @@ Future<bool> loginDialog() async {
|
||||
completer.complete(true);
|
||||
} catch (err) {
|
||||
debugPrint(err.toString());
|
||||
debugPrint(
|
||||
'REMOVE ME ====================== login error ${err.toString()}');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
@@ -438,6 +473,7 @@ Future<bool> loginDialog() async {
|
||||
usernameMsg: usernameMsg,
|
||||
passMsg: passMsg,
|
||||
isInProgress: isInProgress,
|
||||
curOP: curOP,
|
||||
onLogin: onLogin,
|
||||
),
|
||||
const SizedBox(
|
||||
@@ -451,11 +487,19 @@ Future<bool> loginDialog() async {
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
LoginWidgetOP(ops: [
|
||||
ConfigOP(op: 'Github', iconWidth: 24),
|
||||
ConfigOP(op: 'Google', iconWidth: 24),
|
||||
ConfigOP(op: 'Okta', iconWidth: 46),
|
||||
]),
|
||||
LoginWidgetOP(
|
||||
ops: [
|
||||
ConfigOP(op: 'Github', iconWidth: 24),
|
||||
ConfigOP(op: 'Google', iconWidth: 24),
|
||||
ConfigOP(op: 'Okta', iconWidth: 46),
|
||||
],
|
||||
curOP: curOP,
|
||||
cbLogin: (String username) {
|
||||
gFFI.userModel.userName.value = username;
|
||||
completer.complete(true);
|
||||
close();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -291,12 +291,12 @@ class _SettingsState extends State<SettingsPage> 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) {
|
||||
|
||||
@@ -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<FFI> 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<String> _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,7 +75,7 @@ class UserModel {
|
||||
}
|
||||
final userInfo = await bind.mainGetLocalOption(key: 'user_info');
|
||||
if (userInfo.trim().isEmpty) {
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
final m = jsonDecode(userInfo);
|
||||
if (m == null) {
|
||||
@@ -88,10 +89,10 @@ class UserModel {
|
||||
Future<void> 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([
|
||||
@@ -100,30 +101,30 @@ class UserModel {
|
||||
bind.mainSetLocalOption(key: 'selected-tags', value: ''),
|
||||
]);
|
||||
parent.target?.abModel.clear();
|
||||
userName.value = "";
|
||||
userName.value = '';
|
||||
gFFI.dialogManager.dismissByTag(tag);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> 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'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user