From bace94b6d26769f245d291724a84594417b22092 Mon Sep 17 00:00:00 2001 From: Nutcake Date: Tue, 3 Oct 2023 18:20:02 +0200 Subject: [PATCH] Fix user session status not showing --- lib/clients/messaging_client.dart | 81 +++++++------------- lib/config.dart | 2 +- lib/hub_manager.dart | 3 +- lib/models/hub_events.dart | 28 +++++++ lib/models/session.dart | 4 +- lib/models/users/user_status.dart | 90 +++++++++++++++-------- lib/widgets/friends/friend_list_tile.dart | 11 ++- lib/widgets/messages/messages_list.dart | 7 +- pubspec.lock | 4 +- 9 files changed, 130 insertions(+), 100 deletions(-) create mode 100644 lib/models/hub_events.dart diff --git a/lib/clients/messaging_client.dart b/lib/clients/messaging_client.dart index d41083d..6b8acaf 100644 --- a/lib/clients/messaging_client.dart +++ b/lib/clients/messaging_client.dart @@ -1,51 +1,23 @@ import 'dart:async'; -import 'package:contacts_plus_plus/apis/session_api.dart'; + +import 'package:contacts_plus_plus/apis/contact_api.dart'; +import 'package:contacts_plus_plus/apis/message_api.dart'; +import 'package:contacts_plus_plus/apis/user_api.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; +import 'package:contacts_plus_plus/clients/notification_client.dart'; import 'package:contacts_plus_plus/crypto_helper.dart'; import 'package:contacts_plus_plus/hub_manager.dart'; +import 'package:contacts_plus_plus/models/hub_events.dart'; +import 'package:contacts_plus_plus/models/message.dart'; import 'package:contacts_plus_plus/models/session.dart'; +import 'package:contacts_plus_plus/models/users/friend.dart'; import 'package:contacts_plus_plus/models/users/user_status.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; - -import 'package:contacts_plus_plus/apis/contact_api.dart'; -import 'package:contacts_plus_plus/apis/message_api.dart'; -import 'package:contacts_plus_plus/apis/user_api.dart'; -import 'package:contacts_plus_plus/clients/notification_client.dart'; -import 'package:contacts_plus_plus/models/users/friend.dart'; -import 'package:contacts_plus_plus/clients/api_client.dart'; -import 'package:contacts_plus_plus/models/message.dart'; import 'package:package_info_plus/package_info_plus.dart'; -enum EventType { - undefined, - invocation, - streamItem, - completion, - streamInvocation, - cancelInvocation, - ping, - close; -} - -enum EventTarget { - unknown, - messageSent, - receiveMessage, - messagesRead, - receiveSessionUpdate, - removeSession, - receiveStatusUpdate; - - factory EventTarget.parse(String? text) { - if (text == null) return EventTarget.unknown; - return EventTarget.values.firstWhere( - (element) => element.name.toLowerCase() == text.toLowerCase(), - orElse: () => EventTarget.unknown, - ); - } -} class MessagingClient extends ChangeNotifier { static const Duration _autoRefreshDuration = Duration(seconds: 10); @@ -77,11 +49,6 @@ class MessagingClient extends ChangeNotifier { debugPrint("mClient created: $hashCode"); Hive.openBox(_messageBoxKey).then((box) async { await box.delete(_lastUpdateKey); - final activeSessions = await SessionApi.getSessions(apiClient); - for (final session in activeSessions) { - final idHash = CryptoHelper.idHash(session.id + _userStatus.hashSalt); - _sessionMap[idHash] = session; - } _setupHub(); }); } @@ -113,8 +80,6 @@ class MessagingClient extends ChangeNotifier { MessageCache _createUserMessageCache(String userId) => MessageCache(apiClient: _apiClient, userId: userId); - Session? getSessionInfo(String idHash) => _sessionMap[idHash]; - Future refreshFriendsListWithErrorHandler() async { try { await refreshFriendsList(); @@ -155,9 +120,10 @@ class MessagingClient extends ChangeNotifier { Future setUserStatus(UserStatus status) async { final pkginfo = await PackageInfo.fromPlatform(); - _userStatus = status.copyWith( + _userStatus = _userStatus.copyWith( appVersion: "${pkginfo.version} of ${pkginfo.appName}", lastStatusChange: DateTime.now(), + onlineStatus: status.onlineStatus, ); _hubManager.send( @@ -165,14 +131,16 @@ class MessagingClient extends ChangeNotifier { arguments: [ _userStatus.toMap(), { - "group": 0, - "targetIds": [], + "group": 1, + "targetIds": null, } ], ); final self = getAsFriend(_apiClient.userId); - await _updateContact(self!.copyWith(userStatus: _userStatus)); + if (self != null) { + await _updateContact(self.copyWith(userStatus: _userStatus)); + } notifyListeners(); } @@ -284,7 +252,7 @@ class MessagingClient extends ChangeNotifier { _hubManager.setHandler(EventTarget.receiveSessionUpdate, _onReceiveSessionUpdate); await _hubManager.start(); - setUserStatus(userStatus); + await setUserStatus(userStatus); _hubManager.send( "InitializeStatus", responseHandler: (Map data) async { @@ -302,6 +270,10 @@ class MessagingClient extends ChangeNotifier { ); } + Map createSessionMap(String salt) { + return _sessionMap.map((key, value) => MapEntry(CryptoHelper.idHash(value.id + salt), value)); + } + void _onMessageSent(List args) { final msg = args[0]; final message = Message.fromMap(msg, withState: MessageState.sent); @@ -338,9 +310,11 @@ class MessagingClient extends ChangeNotifier { void _onReceiveStatusUpdate(List args) { for (final statusUpdate in args) { - final status = UserStatus.fromMap(statusUpdate); - var friend = getAsFriend(statusUpdate["userId"]); - friend = friend?.copyWith(userStatus: status); + var status = UserStatus.fromMap(statusUpdate); + final sessionMap = createSessionMap(status.hashSalt); + status = status.copyWith( + sessionData: status.sessions.map((e) => sessionMap[e.sessionHash] ?? Session.none()).toList()); + final friend = getAsFriend(statusUpdate["userId"])?.copyWith(userStatus: status); if (friend != null) { _updateContact(friend); } @@ -351,8 +325,7 @@ class MessagingClient extends ChangeNotifier { void _onReceiveSessionUpdate(List args) { for (final sessionUpdate in args) { final session = Session.fromMap(sessionUpdate); - final idHash = CryptoHelper.idHash(session.id + _userStatus.hashSalt); - _sessionMap[idHash] = session; + _sessionMap[session.id] = session; } notifyListeners(); } diff --git a/lib/config.dart b/lib/config.dart index 9d33194..b777fe4 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -5,5 +5,5 @@ class Config { static const int messageCacheValiditySeconds = 90; - static const String latestCompatHash = "jnnkdwkBqGv5+jlf1u/k7A=="; + static const String latestCompatHash = "YPDxN4N9fu7ZgV+Nr/AHQw=="; } \ No newline at end of file diff --git a/lib/hub_manager.dart b/lib/hub_manager.dart index 20a9ef9..8c20861 100644 --- a/lib/hub_manager.dart +++ b/lib/hub_manager.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:collection/collection.dart'; -import 'package:contacts_plus_plus/clients/messaging_client.dart'; import 'package:contacts_plus_plus/config.dart'; +import 'package:contacts_plus_plus/models/hub_events.dart'; import 'package:logging/logging.dart'; import 'package:uuid/uuid.dart'; @@ -81,6 +81,7 @@ class HubManager { case EventType.completion: final handler = _responseHandlers[body["invocationId"]]; handler?.call(body["result"] ?? {}); + _logger.info("Received completion event: $rawType: $body"); break; case EventType.cancelInvocation: case EventType.undefined: diff --git a/lib/models/hub_events.dart b/lib/models/hub_events.dart new file mode 100644 index 0000000..c9c086a --- /dev/null +++ b/lib/models/hub_events.dart @@ -0,0 +1,28 @@ +enum EventType { + undefined, + invocation, + streamItem, + completion, + streamInvocation, + cancelInvocation, + ping, + close; +} + +enum EventTarget { + unknown, + messageSent, + receiveMessage, + messagesRead, + receiveSessionUpdate, + removeSession, + receiveStatusUpdate; + + factory EventTarget.parse(String? text) { + if (text == null) return EventTarget.unknown; + return EventTarget.values.firstWhere( + (element) => element.name.toLowerCase() == text.toLowerCase(), + orElse: () => EventTarget.unknown, + ); + } +} \ No newline at end of file diff --git a/lib/models/session.dart b/lib/models/session.dart index ba112e9..d5993b4 100644 --- a/lib/models/session.dart +++ b/lib/models/session.dart @@ -101,6 +101,7 @@ enum SessionAccessLevel { private, contacts, contactsPlus, + registeredUsers, anyone; static const _readableNamesMap = { @@ -108,6 +109,7 @@ enum SessionAccessLevel { SessionAccessLevel.private: "Private", SessionAccessLevel.contacts: "Contacts", SessionAccessLevel.contactsPlus: "Contacts+", + SessionAccessLevel.registeredUsers: "Registered users", SessionAccessLevel.anyone: "Anyone", }; @@ -119,7 +121,7 @@ enum SessionAccessLevel { } String toReadableString() { - return SessionAccessLevel._readableNamesMap[this] ?? "Unknown"; + return SessionAccessLevel._readableNamesMap[this] ?? SessionAccessLevel.unknown.toReadableString(); } } diff --git a/lib/models/users/user_status.dart b/lib/models/users/user_status.dart index b482b01..197dcec 100644 --- a/lib/models/users/user_status.dart +++ b/lib/models/users/user_status.dart @@ -1,58 +1,81 @@ -import 'dart:convert'; - import 'package:contacts_plus_plus/crypto_helper.dart'; +import 'package:contacts_plus_plus/models/session.dart'; import 'package:contacts_plus_plus/models/session_metadata.dart'; import 'package:contacts_plus_plus/models/users/online_status.dart'; -import 'package:crypto/crypto.dart'; +import 'package:uuid/uuid.dart'; + +enum UserSessionType +{ + unknown, + graphicalClient, + chatClient, + headless, + not; + + factory UserSessionType.fromString(String? text) { + return UserSessionType.values.firstWhere((element) => element.name.toLowerCase() == text?.toLowerCase(), + orElse: () => UserSessionType.unknown, + ); + } +} class UserStatus { final OnlineStatus onlineStatus; final DateTime lastStatusChange; - final int currentSessionAccessLevel; - final bool currentSessionHidden; - final bool currentHosting; + final DateTime lastPresenceTimestamp; + final String userSessionId; final int currentSessionIndex; final List sessions; final String appVersion; final String outputDevice; final bool isMobile; + final bool isPresent; final String compatibilityHash; final String hashSalt; + final UserSessionType sessionType; + final List decodedSessions; const UserStatus({ required this.onlineStatus, required this.lastStatusChange, + required this.lastPresenceTimestamp, + required this.userSessionId, required this.currentSessionIndex, - required this.currentSessionAccessLevel, - required this.currentSessionHidden, - required this.currentHosting, required this.sessions, required this.appVersion, required this.outputDevice, required this.isMobile, + required this.isPresent, required this.compatibilityHash, required this.hashSalt, + required this.sessionType, + this.decodedSessions = const [] }); - factory UserStatus.initial() => UserStatus.empty().copyWith( + factory UserStatus.initial() => + UserStatus.empty().copyWith( onlineStatus: OnlineStatus.online, hashSalt: CryptoHelper.cryptoToken(), outputDevice: "Mobile", + userSessionId: const Uuid().v4().toString(), + sessionType: UserSessionType.chatClient, ); - factory UserStatus.empty() => UserStatus( + factory UserStatus.empty() => + UserStatus( onlineStatus: OnlineStatus.offline, lastStatusChange: DateTime.now(), - currentSessionAccessLevel: 0, - currentSessionHidden: false, - currentHosting: false, + lastPresenceTimestamp: DateTime.now(), + userSessionId: "", currentSessionIndex: -1, sessions: [], appVersion: "", outputDevice: "Unknown", isMobile: false, + isPresent: false, compatibilityHash: "", hashSalt: "", + sessionType: UserSessionType.unknown ); factory UserStatus.fromMap(Map map) { @@ -60,10 +83,10 @@ class UserStatus { final status = OnlineStatus.fromString(statusString); return UserStatus( onlineStatus: status, - lastStatusChange: DateTime.parse(map["lastStatusChange"]), - currentSessionAccessLevel: map["currentSessionAccessLevel"] ?? 0, - currentSessionHidden: map["currentSessionHidden"] ?? false, - currentHosting: map["currentHosting"] ?? false, + lastStatusChange: DateTime.tryParse(map["lastStatusChange"] ?? "") ?? DateTime.now(), + lastPresenceTimestamp: DateTime.tryParse(map["lastPresenceTimestamp"] ?? "") ?? DateTime.now(), + userSessionId: map["userSessionId"] ?? "", + isPresent: map["isPresent"] ?? false, currentSessionIndex: map["currentSessionIndex"] ?? -1, sessions: (map["sessions"] as List? ?? []).map((e) => SessionMetadata.fromMap(e)).toList(), appVersion: map["appVersion"] ?? "", @@ -71,6 +94,7 @@ class UserStatus { isMobile: map["isMobile"] ?? false, compatibilityHash: map["compatabilityHash"] ?? "", hashSalt: map["hashSalt"] ?? "", + sessionType: UserSessionType.fromString(map["sessionType"]) ); } @@ -78,17 +102,17 @@ class UserStatus { return { "onlineStatus": onlineStatus.index, "lastStatusChange": lastStatusChange.toIso8601String(), - "currentSessionAccessLevel": currentSessionAccessLevel, - "currentSessionHidden": currentSessionHidden, - "currentHosting": currentHosting, + "isPresent": isPresent, + "lastPresenceTimestamp": lastPresenceTimestamp.toIso8601String(), + "userSessionId": userSessionId, "currentSessionIndex": currentSessionIndex, "sessions": shallow ? [] : sessions - .map( - (e) => e.toMap(), - ) - .toList(), + .map( + (e) => e.toMap(), + ) + .toList(), "appVersion": appVersion, "outputDevice": outputDevice, "isMobile": isMobile, @@ -99,9 +123,9 @@ class UserStatus { UserStatus copyWith({ OnlineStatus? onlineStatus, DateTime? lastStatusChange, - int? currentSessionAccessLevel, - bool? currentSessionHidden, - bool? currentHosting, + DateTime? lastPresenceTimestamp, + bool? isPresent, + String? userSessionId, int? currentSessionIndex, List? sessions, String? appVersion, @@ -109,13 +133,15 @@ class UserStatus { bool? isMobile, String? compatibilityHash, String? hashSalt, + UserSessionType? sessionType, + List? sessionData, }) => UserStatus( onlineStatus: onlineStatus ?? this.onlineStatus, lastStatusChange: lastStatusChange ?? this.lastStatusChange, - currentSessionAccessLevel: currentSessionAccessLevel ?? this.currentSessionAccessLevel, - currentSessionHidden: currentSessionHidden ?? this.currentSessionHidden, - currentHosting: currentHosting ?? this.currentHosting, + lastPresenceTimestamp: lastPresenceTimestamp ?? this.lastPresenceTimestamp, + isPresent: isPresent ?? this.isPresent, + userSessionId: userSessionId ?? this.userSessionId, currentSessionIndex: currentSessionIndex ?? this.currentSessionIndex, sessions: sessions ?? this.sessions, appVersion: appVersion ?? this.appVersion, @@ -123,5 +149,7 @@ class UserStatus { isMobile: isMobile ?? this.isMobile, compatibilityHash: compatibilityHash ?? this.compatibilityHash, hashSalt: hashSalt ?? this.hashSalt, + sessionType: sessionType ?? this.sessionType, + decodedSessions: sessionData ?? this.decodedSessions, ); } diff --git a/lib/widgets/friends/friend_list_tile.dart b/lib/widgets/friends/friend_list_tile.dart index cdef4cb..8d1a6b5 100644 --- a/lib/widgets/friends/friend_list_tile.dart +++ b/lib/widgets/friends/friend_list_tile.dart @@ -1,9 +1,7 @@ -import 'dart:math'; - import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/clients/messaging_client.dart'; -import 'package:contacts_plus_plus/models/users/friend.dart'; import 'package:contacts_plus_plus/models/message.dart'; +import 'package:contacts_plus_plus/models/users/friend.dart'; import 'package:contacts_plus_plus/widgets/formatted_text.dart'; import 'package:contacts_plus_plus/widgets/friends/friend_online_status_indicator.dart'; import 'package:contacts_plus_plus/widgets/generic_avatar.dart'; @@ -24,8 +22,9 @@ class FriendListTile extends StatelessWidget { final imageUri = Aux.resdbToHttp(friend.userProfile.iconUrl); final theme = Theme.of(context); final mClient = Provider.of(context, listen: false); - final currentSessionMetadata = friend.userStatus.sessions.elementAtOrNull(max(0, friend.userStatus.currentSessionIndex)); - final currentSession = mClient.getSessionInfo(currentSessionMetadata?.sessionHash ?? ""); + final currentSession = friend.userStatus.currentSessionIndex == -1 + ? null + : friend.userStatus.decodedSessions.elementAtOrNull(friend.userStatus.currentSessionIndex); return ListTile( leading: GenericAvatar( imageUri: imageUri, @@ -59,7 +58,7 @@ class FriendListTile extends StatelessWidget { width: 4, ), Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"), - if (currentSession != null) ...[ + if (currentSession != null && !currentSession.isNone) ...[ const Text(" in "), Expanded( child: FormattedText( diff --git a/lib/widgets/messages/messages_list.dart b/lib/widgets/messages/messages_list.dart index 2c5aa5d..822e8b0 100644 --- a/lib/widgets/messages/messages_list.dart +++ b/lib/widgets/messages/messages_list.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:contacts_plus_plus/clients/audio_cache_client.dart'; import 'package:contacts_plus_plus/clients/messaging_client.dart'; import 'package:contacts_plus_plus/models/users/friend.dart'; @@ -56,7 +57,7 @@ class _MessagesListState extends State with SingleTickerProviderSt return Consumer(builder: (context, mClient, _) { final friend = mClient.selectedFriend ?? Friend.empty(); final cache = mClient.getUserMessageCache(friend.id); - final sessions = friend.userStatus.sessions; + final sessions = friend.userStatus.decodedSessions.whereNot((element) => element.isNone).toList(); return Scaffold( appBar: AppBar( title: Row( @@ -121,9 +122,7 @@ class _MessagesListState extends State with SingleTickerProviderSt scrollDirection: Axis.horizontal, itemCount: sessions.length, itemBuilder: (context, index) { - final currentSessionMetadata = sessions[index]; - final currentSession = mClient.getSessionInfo(currentSessionMetadata.sessionHash); - if (currentSession == null) return null; + final currentSession = sessions[index]; return SessionTile(session: currentSession); }, ), diff --git a/pubspec.lock b/pubspec.lock index 86fa7b6..89ee28b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" file: dependency: transitive description: