Resolves #338 OAuth with Home Assistant
This commit is contained in:
parent
6a03105d01
commit
67d7bb45f5
@ -3,6 +3,7 @@ part of 'main.dart';
|
||||
class HomeAssistant {
|
||||
String _webSocketAPIEndpoint;
|
||||
String httpWebHost;
|
||||
String oauthUrl;
|
||||
//String _password;
|
||||
String _token;
|
||||
String _tempToken;
|
||||
@ -68,6 +69,7 @@ class HomeAssistant {
|
||||
throw("Check connection settings");
|
||||
} else {
|
||||
isSettingsLoaded = true;
|
||||
oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent('http://ha-client.homemade.systems/')}&redirect_uri=${Uri.encodeComponent('http://ha-client.homemade.systems/service/auth_callback.html')}";
|
||||
entities = EntityCollection(httpWebHost);
|
||||
}
|
||||
}
|
||||
@ -85,6 +87,7 @@ class HomeAssistant {
|
||||
} else {
|
||||
Logger.d("Fetching...");
|
||||
_fetchCompleter = new Completer();
|
||||
_fetchTimer?.cancel();
|
||||
_fetchTimer = Timer(fetchTimeout, () {
|
||||
Logger.e( "Data fetching timeout");
|
||||
disconnect().then((_) {
|
||||
@ -104,13 +107,12 @@ class HomeAssistant {
|
||||
}
|
||||
|
||||
disconnect() async {
|
||||
if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
|
||||
await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
|
||||
Logger.d( "Socket disconnecting...");
|
||||
await _socketSubscription?.cancel();
|
||||
await _hassioChannel?.sink?.close()?.timeout(Duration(seconds: 3),
|
||||
onTimeout: () => Logger.d( "Socket sink closed")
|
||||
);
|
||||
await _socketSubscription.cancel();
|
||||
_hassioChannel = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -119,6 +121,7 @@ class HomeAssistant {
|
||||
Logger.d("Previous connection is not complited");
|
||||
} else {
|
||||
if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
|
||||
_connectionTimer?.cancel();
|
||||
_connectionCompleter = new Completer();
|
||||
autoReconnect = false;
|
||||
disconnect().then((_){
|
||||
@ -127,9 +130,7 @@ class HomeAssistant {
|
||||
Logger.e( "Socket connection timeout");
|
||||
_handleSocketError(null);
|
||||
});
|
||||
if (_socketSubscription != null) {
|
||||
_socketSubscription.cancel();
|
||||
}
|
||||
_socketSubscription?.cancel();
|
||||
_hassioChannel = IOWebSocketChannel.connect(
|
||||
_webSocketAPIEndpoint, pingInterval: Duration(seconds: 30));
|
||||
_socketSubscription = _hassioChannel.stream.listen(
|
||||
@ -199,7 +200,7 @@ class HomeAssistant {
|
||||
}
|
||||
|
||||
void _completeFetching(error) {
|
||||
_fetchTimer.cancel();
|
||||
_fetchTimer?.cancel();
|
||||
_completeConnecting(error);
|
||||
if (!_fetchCompleter.isCompleted) {
|
||||
if (error != null) {
|
||||
@ -213,7 +214,7 @@ class HomeAssistant {
|
||||
}
|
||||
|
||||
void _completeConnecting(error) {
|
||||
_connectionTimer.cancel();
|
||||
_connectionTimer?.cancel();
|
||||
if (!_connectionCompleter.isCompleted) {
|
||||
if (error != null) {
|
||||
_connectionCompleter.completeError(error);
|
||||
@ -241,10 +242,10 @@ class HomeAssistant {
|
||||
_sendSubscribe();
|
||||
} else if (data["type"] == "auth_invalid") {
|
||||
Logger.d("[Received] <== ${data.toString()}");
|
||||
//TODO remove token and login again
|
||||
_completeConnecting({"errorCode": 6, "errorMessage": "${data["message"]}"});
|
||||
_logout();
|
||||
_completeConnecting({"errorCode": 62, "errorMessage": "${data["message"]}"});
|
||||
} else if (data["type"] == "result") {
|
||||
Logger.d("[Received] <== ${data.toString()}");
|
||||
Logger.d("[Received] <== id: ${data['id']}, success: ${data['success']}");
|
||||
_messageResolver[data["id"]]?.complete(data);
|
||||
_messageResolver.remove(data["id"]);
|
||||
} else if (data["type"] == "event") {
|
||||
@ -261,6 +262,12 @@ class HomeAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
void _logout() {
|
||||
_token = null;
|
||||
_tempToken = null;
|
||||
SharedPreferences.getInstance().then((prefs) => prefs.remove("hassio-token"));
|
||||
}
|
||||
|
||||
void _sendSubscribe() {
|
||||
_incrementMessageId();
|
||||
_subscriptionMessageId = _currentMessageId;
|
||||
@ -276,18 +283,19 @@ class HomeAssistant {
|
||||
}
|
||||
|
||||
Future _getLongLivedToken() async {
|
||||
await _sendSocketMessage(type: "auth/long_lived_access_token", additionalData: {"client_name": "HA Client 3", "client_icon": null, "lifespan": 365}).then((data) {
|
||||
await _sendSocketMessage(type: "auth/long_lived_access_token", additionalData: {"client_name": "HA Client app", "lifespan": 365}).then((data) {
|
||||
if (data['success']) {
|
||||
Logger.d("Got long-lived token: ${data['result']}");
|
||||
_token = data['result'];
|
||||
//TODO save token
|
||||
_tempToken = null;
|
||||
SharedPreferences.getInstance().then((prefs) => prefs.setString("hassio-token", _token));
|
||||
} else {
|
||||
_logout();
|
||||
Logger.e("Error getting long-lived token: ${data['error'].toString()}");
|
||||
//TODO DO DO something here
|
||||
}
|
||||
}).catchError((e) {
|
||||
Logger.e("Error getting long-lived token: ${e.toString()}");
|
||||
//TODO DO DO something here
|
||||
_logout();
|
||||
});
|
||||
}
|
||||
|
||||
@ -337,7 +345,6 @@ class HomeAssistant {
|
||||
Logger.d( "No long leaved token. Need to authenticate.");
|
||||
final flutterWebviewPlugin = new FlutterWebviewPlugin();
|
||||
flutterWebviewPlugin.onUrlChanged.listen((String url) {
|
||||
Logger.d("Launched url: $url");
|
||||
if (url.startsWith("http://ha-client.homemade.systems/service/auth_callback.html")) {
|
||||
String authCode = url.split("=")[1];
|
||||
Logger.d("We have auth code. Getting temporary access token...");
|
||||
@ -354,27 +361,33 @@ class HomeAssistant {
|
||||
Logger.d("Firing event to reload UI");
|
||||
eventBus.fire(ReloadUIEvent());
|
||||
}).catchError((e) {
|
||||
//TODO DO DO something here
|
||||
_logout();
|
||||
disconnect();
|
||||
flutterWebviewPlugin.close();
|
||||
_completeFetching({"errorCode": 61, "errorMessage": "Error getting temp token"});
|
||||
Logger.e("Error getting temp token: ${e.toString()}");
|
||||
});
|
||||
}
|
||||
});
|
||||
disconnect().then((_){
|
||||
//TODO create special error code to show "Login" in message
|
||||
_completeConnecting({"errorCode": 6, "errorMessage": "Not authenticated"});
|
||||
});
|
||||
String oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent('http://ha-client.homemade.systems/')}&redirect_uri=${Uri.encodeComponent('http://ha-client.homemade.systems/service/auth_callback.html')}";
|
||||
Logger.d("OAuth url: $oauthUrl");
|
||||
eventBus.fire(StartAuthEvent(oauthUrl));
|
||||
disconnect();
|
||||
_completeFetching({"errorCode": 60, "errorMessage": "Not authenticated"});
|
||||
_requestOAuth();
|
||||
} else if (_tempToken != null) {
|
||||
Logger.d("We have temp token. Login...");
|
||||
_hassioChannel.sink.add('{"type": "auth","access_token": "$_tempToken"}');
|
||||
} else {
|
||||
Logger.e("General login error");
|
||||
//TODO DO DO something here
|
||||
_logout();
|
||||
disconnect();
|
||||
_completeFetching({"errorCode": 61, "errorMessage": "General login error"});
|
||||
}
|
||||
}
|
||||
|
||||
void _requestOAuth() {
|
||||
Logger.d("OAuth url: $oauthUrl");
|
||||
eventBus.fire(StartAuthEvent(oauthUrl));
|
||||
}
|
||||
|
||||
Future _sendSocketMessage({String type, Map additionalData, bool noId: false}) {
|
||||
Completer _completer = Completer();
|
||||
Map dataObject = {"type": "$type"};
|
||||
|
@ -104,8 +104,6 @@ EventBus eventBus = new EventBus();
|
||||
const String appName = "HA Client";
|
||||
const appVersion = "0.5.2";
|
||||
|
||||
//String homeAssistantWebHost;
|
||||
|
||||
void main() {
|
||||
FlutterError.onError = (errorDetails) {
|
||||
Logger.e( "${errorDetails.exception}");
|
||||
@ -158,12 +156,7 @@ class MainPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MainPageState extends State<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin {
|
||||
//HomeAssistant _homeAssistant;
|
||||
//Map _instanceConfig;
|
||||
//String _webSocketApiEndpoint;
|
||||
//String _password;
|
||||
//int _uiViewsCount = 0;
|
||||
//String _instanceHost;
|
||||
|
||||
StreamSubscription _stateSubscription;
|
||||
StreamSubscription _settingsSubscription;
|
||||
StreamSubscription _serviceCallSubscription;
|
||||
@ -171,11 +164,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
StreamSubscription _showErrorSubscription;
|
||||
StreamSubscription _startAuthSubscription;
|
||||
StreamSubscription _reloadUISubscription;
|
||||
//bool _settingsLoaded = false;
|
||||
bool _accountMenuExpanded = false;
|
||||
//bool _useLovelaceUI;
|
||||
int _previousViewCount;
|
||||
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||
//final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -213,23 +204,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
}
|
||||
}
|
||||
|
||||
/*_loadConnectionSettings() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String domain = prefs.getString('hassio-domain');
|
||||
String port = prefs.getString('hassio-port');
|
||||
_instanceHost = "$domain:$port";
|
||||
_webSocketApiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
|
||||
homeAssistantWebHost = "${prefs.getString('hassio-res-protocol')}://$domain:$port";
|
||||
_password = prefs.getString('hassio-password');
|
||||
_useLovelaceUI = prefs.getBool('use-lovelace') ?? true;
|
||||
if ((domain == null) || (port == null) || (_password == null) ||
|
||||
(domain.length == 0) || (port.length == 0) || (_password.length == 0)) {
|
||||
throw("Check connection settings");
|
||||
} else {
|
||||
_settingsLoaded = true;
|
||||
}
|
||||
}*/
|
||||
|
||||
_subscribe() {
|
||||
if (_stateSubscription == null) {
|
||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||
@ -269,20 +243,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
|
||||
if (_startAuthSubscription == null) {
|
||||
_startAuthSubscription = eventBus.on<StartAuthEvent>().listen((event){
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WebviewScaffold(
|
||||
url: "${event.oauthUrl}",
|
||||
appBar: new AppBar(
|
||||
title: new Text("Login"),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
_showOAuth();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*_firebaseMessaging.getToken().then((String token) {
|
||||
//Logger.d("FCM token: $token");
|
||||
widget.homeAssistant.sendHTTPPost(
|
||||
@ -303,6 +269,20 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
);*/
|
||||
}
|
||||
|
||||
void _showOAuth() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WebviewScaffold(
|
||||
url: "${widget.homeAssistant.oauthUrl}",
|
||||
appBar: new AppBar(
|
||||
title: new Text("Login"),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_refreshData() async {
|
||||
//widget.homeAssistant.updateSettings(_webSocketApiEndpoint, _password, _useLovelaceUI);
|
||||
_hideBottomBar();
|
||||
@ -539,12 +519,31 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
break;
|
||||
}
|
||||
|
||||
case 6: {
|
||||
case 60: {
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Settings", style: textStyle),
|
||||
child: Text("Login", style: textStyle),
|
||||
onPressed: () {
|
||||
//_scaffoldKey?.currentState?.hideCurrentSnackBar();
|
||||
Navigator.pushNamed(context, '/connection-settings');
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 61: {
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Try again", style: textStyle),
|
||||
onPressed: () {
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 62: {
|
||||
_bottomBarAction = FlatButton(
|
||||
child: Text("Login again", style: textStyle),
|
||||
onPressed: () {
|
||||
_refreshData();
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
@ -14,8 +14,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
String _newHassioDomain = "";
|
||||
String _hassioPort = "";
|
||||
String _newHassioPort = "";
|
||||
String _hassioPassword = "";
|
||||
String _newHassioPassword = "";
|
||||
String _socketProtocol = "wss";
|
||||
String _newSocketProtocol = "wss";
|
||||
bool _useLovelace = true;
|
||||
@ -36,7 +34,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
setState(() {
|
||||
_hassioDomain = _newHassioDomain = prefs.getString("hassio-domain")?? "";
|
||||
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
|
||||
_hassioPassword = _newHassioPassword = prefs.getString("hassio-password") ?? "";
|
||||
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
|
||||
try {
|
||||
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? true;
|
||||
@ -47,7 +44,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
}
|
||||
|
||||
bool _checkConfigChanged() {
|
||||
return ((_newHassioPassword != _hassioPassword) ||
|
||||
return (
|
||||
(_newHassioPort != _hassioPort) ||
|
||||
(_newHassioDomain != _hassioDomain) ||
|
||||
(_newSocketProtocol != _socketProtocol) ||
|
||||
@ -62,7 +59,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString("hassio-domain", _newHassioDomain);
|
||||
prefs.setString("hassio-port", _newHassioPort);
|
||||
prefs.setString("hassio-password", _newHassioPassword);
|
||||
prefs.setString("hassio-protocol", _newSocketProtocol);
|
||||
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
|
||||
prefs.setBool("use-lovelace", _newUseLovelace);
|
||||
@ -152,21 +148,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
"Try ports 80 and 443 if default is not working and you don't know why.",
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
new TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Access token"
|
||||
),
|
||||
controller: new TextEditingController.fromValue(
|
||||
new TextEditingValue(
|
||||
text: _newHassioPassword,
|
||||
selection:
|
||||
new TextSelection.collapsed(offset: _newHassioPassword.length)
|
||||
)
|
||||
),
|
||||
onChanged: (value) {
|
||||
_newHassioPassword = value;
|
||||
}
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: Text(
|
||||
|
Reference in New Issue
Block a user