diff --git a/CHANGELOG.md b/CHANGELOG.md index 6090b663..e00428e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [3.1.6] - Nov 3, 2021 +* Added `RestrictedUser` for callback mute/ban feature +* Fixed session related issue +* Fixed group channel updates not to apply unset `operator_ids` in `GroupChannelParams` + ## [3.1.5] - Sep 30, 2021 * Fixed link in README * Improved stabilities diff --git a/lib/constant/enums.dart b/lib/constant/enums.dart index f3fbf596..6f9d3811 100644 --- a/lib/constant/enums.dart +++ b/lib/constant/enums.dart @@ -606,3 +606,8 @@ const memberListOrderEnumMap = { MemberListOrder.operatorThenMemberNicknameAlphabetical: 'operator_then_member_alphabetical', }; + +enum RestrictionType { + muted, + banned, +} diff --git a/lib/core/models/member.dart b/lib/core/models/member.dart index 07f540ac..71b75541 100644 --- a/lib/core/models/member.dart +++ b/lib/core/models/member.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:sendbird_sdk/constant/enums.dart'; +import 'package:sendbird_sdk/core/models/restricted_user.dart'; import 'package:sendbird_sdk/core/models/user.dart'; import 'package:sendbird_sdk/utils/json_from_parser.dart'; @@ -31,6 +32,10 @@ class Member extends User { @JsonKey(defaultValue: Role.none, unknownEnumValue: Role.none) Role role; + /// Restriction information + @JsonKey(ignore: true) + RestrictionInfo? restrictionInfo; + Member({ this.state = MemberState.none, this.isBlockedByMe = false, @@ -64,7 +69,11 @@ class Member extends User { // json serialization - factory Member.fromJson(Map json) => _$MemberFromJson(json); + factory Member.fromJson(Map json) { + final member = _$MemberFromJson(json); + member.restrictionInfo = RestrictionInfo.fromJson(json); + return member; + } @override Map toJson() => _$MemberToJson(this); diff --git a/lib/core/models/responses.dart b/lib/core/models/responses.dart index cf865689..346fd0af 100644 --- a/lib/core/models/responses.dart +++ b/lib/core/models/responses.dart @@ -3,6 +3,8 @@ import 'package:sendbird_sdk/core/channel/base/base_channel.dart'; import 'package:sendbird_sdk/core/channel/group/group_channel.dart'; import 'package:sendbird_sdk/core/channel/open/open_channel.dart'; import 'package:sendbird_sdk/core/message/base_message.dart'; +import 'package:sendbird_sdk/core/models/member.dart'; +import 'package:sendbird_sdk/core/models/restricted_user.dart'; import 'package:sendbird_sdk/core/models/user.dart'; import 'package:sendbird_sdk/services/db/cache_service.dart'; @@ -158,16 +160,17 @@ class OperatorListQueryResponse extends BaseResponse { } @JsonSerializable(createToJson: false) -class UserListQueryResponse extends BaseResponse { +class UserListQueryResponse extends BaseResponse { @JsonKey(defaultValue: []) - List users; + @UserConverter() + List users; String? next; UserListQueryResponse({this.users = const [], this.next}); factory UserListQueryResponse.fromJson(Map json) => - _$UserListQueryResponseFromJson(json); + _$UserListQueryResponseFromJson(json); } @JsonSerializable(createToJson: false) @@ -204,6 +207,30 @@ class ChannelConverter implements JsonConverter { } } +class UserConverter implements JsonConverter { + const UserConverter(); + + @override + T fromJson(Object json) { + if (json is Map) { + if (json.containsKey('end_at')) { + return RestrictedUser.fromJson(json) as T; + } else if (json.containsKey('muted_end_at') || + json.containsKey('is_muted')) { + return Member.fromJson(json) as T; + } else { + return User.fromJson(json) as T; + } + } + return json as T; + } + + @override + Object toJson(T object) { + return object as Object; + } +} + @JsonSerializable(createToJson: false) class MessageSearchQueryResponse extends BaseResponse { @JsonKey(defaultValue: []) diff --git a/lib/core/models/responses.g.dart b/lib/core/models/responses.g.dart index 35abcd00..7afc0eb3 100644 --- a/lib/core/models/responses.g.dart +++ b/lib/core/models/responses.g.dart @@ -83,11 +83,11 @@ OperatorListQueryResponse _$OperatorListQueryResponseFromJson( ); } -UserListQueryResponse _$UserListQueryResponseFromJson( +UserListQueryResponse _$UserListQueryResponseFromJson( Map json) { - return UserListQueryResponse( + return UserListQueryResponse( users: (json['users'] as List?) - ?.map((e) => User.fromJson(e as Map)) + ?.map((e) => UserConverter().fromJson(e as Object)) .toList() ?? [], next: json['next'] as String?, diff --git a/lib/core/models/restricted_user.dart b/lib/core/models/restricted_user.dart new file mode 100644 index 00000000..c880abbe --- /dev/null +++ b/lib/core/models/restricted_user.dart @@ -0,0 +1,81 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_sdk/constant/enums.dart'; +import 'package:sendbird_sdk/core/models/user.dart'; +import 'package:sendbird_sdk/utils/json_from_parser.dart'; + +part 'restricted_user.g.dart'; + +/// An object represents a sender of a message. +@JsonSerializable() +class RestrictedUser extends User { + /// Information about restriction + RestrictionInfo? get restrictionInfo => _info; + RestrictionInfo? _info; + + RestrictedUser({ + required String userId, + required String nickname, + String? profileUrl, + UserConnectionStatus connectionStatus = UserConnectionStatus.notAvailable, + int? lastSeenAt, + List? preferredLanguages, + String? friendDiscoveryKey, + String? friendName, + List? discoveryKeys, + Map metaData = const {}, + bool requireAuth = false, + }) : super( + userId: userId, + nickname: nickname, + profileUrl: profileUrl, + connectionStatus: connectionStatus, + lastSeenAt: lastSeenAt, + preferredLanguages: preferredLanguages, + friendDiscoveryKey: friendDiscoveryKey, + friendName: friendName, + discoveryKeys: discoveryKeys, + metaData: metaData, + requireAuth: requireAuth, + ); + + factory RestrictedUser.fromJson(Map json) { + final user = _$RestrictedUserFromJson(json); + user._info = RestrictionInfo.fromJson(json); + return user; + } + + @override + Map toJson() => _$RestrictedUserToJson(this); +} + +@JsonSerializable() +class RestrictionInfo { + // description of restriction + final String? description; + + // timestamp when restriction will end + final int? endAt; + + // timestamp when restriction started + final int? startAt; + + // restriction type + final RestrictionType type; + + RestrictionInfo({ + this.description, + this.endAt, + this.startAt, + this.type = RestrictionType.muted, + }); + + factory RestrictionInfo.fromJson(Map json) { + json['end_at'] = json['end_at'] ?? json['muted_end_at']; + json['start_at'] = json['start_at'] ?? json['muted_start_at']; + json['description'] = json['description'] ?? json['muted_description']; + if (json['type'] == null) json['type'] = 'muted'; + return _$RestrictionInfoFromJson(json); + } + + Map toJson() => _$RestrictionInfoToJson(this); +} diff --git a/lib/core/models/restricted_user.g.dart b/lib/core/models/restricted_user.g.dart new file mode 100644 index 00000000..7ce94987 --- /dev/null +++ b/lib/core/models/restricted_user.g.dart @@ -0,0 +1,97 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'restricted_user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RestrictedUser _$RestrictedUserFromJson(Map json) { + return RestrictedUser( + userId: json['user_id'] as String, + nickname: json['nickname'] as String? ?? '', + profileUrl: json['profile_url'] as String?, + connectionStatus: boolToConnectionStatus(json['is_online'] as bool?), + lastSeenAt: json['last_seen_at'] as int?, + preferredLanguages: (json['preferred_languages'] as List?) + ?.map((e) => e as String) + .toList(), + friendDiscoveryKey: json['friend_discovery_key'] as String?, + friendName: json['friend_name'] as String?, + discoveryKeys: (json['discovery_keys'] as List?) + ?.map((e) => e as String) + .toList(), + metaData: (json['metadata'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ) ?? + {}, + requireAuth: json['require_auth_for_profile_image'] as bool? ?? false, + ) + ..isActive = json['is_active'] as bool? + ..sessionToken = json['session_token'] as String?; +} + +Map _$RestrictedUserToJson(RestrictedUser instance) => + { + 'user_id': instance.userId, + 'nickname': instance.nickname, + 'profile_url': instance.profileUrl, + 'is_online': connectionStatusToBool(instance.connectionStatus), + 'last_seen_at': instance.lastSeenAt, + 'is_active': instance.isActive, + 'preferred_languages': instance.preferredLanguages, + 'friend_discovery_key': instance.friendDiscoveryKey, + 'friend_name': instance.friendName, + 'discovery_keys': instance.discoveryKeys, + 'metadata': instance.metaData, + 'require_auth_for_profile_image': instance.requireAuth, + 'session_token': instance.sessionToken, + }; + +RestrictionInfo _$RestrictionInfoFromJson(Map json) { + return RestrictionInfo( + description: json['description'] as String?, + endAt: json['end_at'] as int?, + startAt: json['start_at'] as int?, + type: _$enumDecode(_$RestrictionTypeEnumMap, json['type']), + ); +} + +Map _$RestrictionInfoToJson(RestrictionInfo instance) => + { + 'description': instance.description, + 'end_at': instance.endAt, + 'start_at': instance.startAt, + 'type': _$RestrictionTypeEnumMap[instance.type], + }; + +K _$enumDecode( + Map enumValues, + Object? source, { + K? unknownValue, +}) { + if (source == null) { + throw ArgumentError( + 'A value must be provided. Supported values: ' + '${enumValues.values.join(', ')}', + ); + } + + return enumValues.entries.singleWhere( + (e) => e.value == source, + orElse: () { + if (unknownValue == null) { + throw ArgumentError( + '`$source` is not one of the supported values: ' + '${enumValues.values.join(', ')}', + ); + } + return MapEntry(unknownValue, enumValues.values.first); + }, + ).key; +} + +const _$RestrictionTypeEnumMap = { + RestrictionType.muted: 'muted', + RestrictionType.banned: 'banned', +}; diff --git a/lib/events/session_event.dart b/lib/events/session_event.dart index d4c01c99..4beeeb40 100644 --- a/lib/events/session_event.dart +++ b/lib/events/session_event.dart @@ -6,9 +6,6 @@ part 'session_event.g.dart'; /// Represents session information @JsonSerializable(createToJson: false) class SessionEvent extends BaseEvent { - @JsonKey(defaultValue: 0) - final int? expiresAt; - final String? newKey; final String? ekey; @@ -17,7 +14,7 @@ class SessionEvent extends BaseEvent { String get sessionKey => key ?? newKey ?? ''; - SessionEvent({this.expiresAt, this.newKey, this.ekey, this.key}); + SessionEvent({this.newKey, this.ekey, this.key}); factory SessionEvent.fromJson(Map json) => _$SessionEventFromJson(json); diff --git a/lib/events/session_event.g.dart b/lib/events/session_event.g.dart index b06f5ad5..a012c453 100644 --- a/lib/events/session_event.g.dart +++ b/lib/events/session_event.g.dart @@ -8,7 +8,6 @@ part of 'session_event.dart'; SessionEvent _$SessionEventFromJson(Map json) { return SessionEvent( - expiresAt: json['expires_at'] as int? ?? 0, newKey: json['new_key'] as String?, ekey: json['ekey'] as String?, key: json['key'] as String?, diff --git a/lib/managers/command_manager.dart b/lib/managers/command_manager.dart index 124b7a5c..6591b41c 100644 --- a/lib/managers/command_manager.dart +++ b/lib/managers/command_manager.dart @@ -12,6 +12,7 @@ import 'package:sendbird_sdk/core/models/command.dart'; import 'package:sendbird_sdk/core/models/error.dart'; import 'package:sendbird_sdk/core/models/member.dart'; import 'package:sendbird_sdk/core/models/reconnect_task.dart'; +import 'package:sendbird_sdk/core/models/restricted_user.dart'; import 'package:sendbird_sdk/core/models/unread_count_info.dart'; import 'package:sendbird_sdk/core/models/user.dart'; import 'package:sendbird_sdk/events/channel_event.dart'; @@ -263,10 +264,7 @@ class CommandManager with SdkAccessor { ..reconnecting = false; sdk.api.currentUserId = user.userId; - sdk.api.initialize( - sessionKey: event.sessionKey, - uploadSizeLimit: event.appInfo.uploadSizeLimit, - ); + sdk.api.initialize(uploadSizeLimit: event.appInfo.uploadSizeLimit); sdk.sessionManager ..setUserId(user.userId) @@ -289,7 +287,8 @@ class CommandManager with SdkAccessor { } Future _processSessionExpired(Command cmd) async { - eventManager.notifySessionExpired(); + sdk.sessionManager.setSessionKey(null); + sdk.state.sessionKey = null; await sdk.sessionManager.updateSession(); } @@ -359,7 +358,8 @@ class CommandManager with SdkAccessor { final message = await parseMessage(cmd); if (message == null) throw UnrecognizedMessageTypeError(); - final currentUser = appState.currentUser!; + final currentUser = appState.currentUser; + if (currentUser == null) return; final channel = await BaseChannel.getBaseChannel( message.channelType, message.channelUrl, @@ -527,7 +527,6 @@ class CommandManager with SdkAccessor { Future _processSessionRefresh(Command cmd) async { final event = SessionEvent.fromJson(cmd.payload); sdk.sessionManager.setSessionKey(event.sessionKey); - sdk.sessionManager.setSessionExpiresAt(event.expiresAt); } Future _processError(Command cmd) async { @@ -634,7 +633,9 @@ class CommandManager with SdkAccessor { Future _processBan(ChannelEvent event, bool banned) async { try { - final user = User.fromJson(event.data); + final user = banned + ? RestrictedUser.fromJson(event.data) + : User.fromJson(event.data); final channel = await BaseChannel.getBaseChannel( event.channelType, event.channelUrl, @@ -670,7 +671,9 @@ class CommandManager with SdkAccessor { Future _processMute(ChannelEvent event, bool muted) async { try { - final user = User.fromJson(event.data); + final user = muted + ? RestrictedUser.fromJson(event.data) + : User.fromJson(event.data); final channel = await BaseChannel.getBaseChannel( event.channelType, event.channelUrl, @@ -682,6 +685,8 @@ class CommandManager with SdkAccessor { final member = channel.members.firstWhereOrNull((e) => e.userId == user.userId); + member?.restrictionInfo = + muted ? RestrictionInfo.fromJson(event.data) : null; member?.isMuted = muted; } diff --git a/lib/managers/session_manager.dart b/lib/managers/session_manager.dart index 91875b8b..def19430 100644 --- a/lib/managers/session_manager.dart +++ b/lib/managers/session_manager.dart @@ -19,18 +19,20 @@ class SessionManager with SdkAccessor { String? _accessToken; late String _sessionKeyPath; late String _userIdKeyPath; - int _sessionExpiresAt = 0; bool isRefreshingKey = false; late Future Function(String) successFunc; late Function errorFunc; + late List sessionUpdateCompleters; + SessionManager() { _userIdKeyPath = 'com.sendbird.sdk.messaging.userid'; _sessionKeyPath = 'com.sendbird.sdk.messaging.sessionkey'; successFunc = _sessionSuccessHandler; errorFunc = _sessionErrorHandler; + sessionUpdateCompleters = []; } String? get accessToken => _accessToken; @@ -52,12 +54,6 @@ class SessionManager with SdkAccessor { _userIdKeyPath = path; } - void setSessionExpiresAt(int? timestamp) { - _sessionExpiresAt = timestamp ?? 0; - } - - int get sessionExpiresAt => _sessionExpiresAt; - /// Set a `sessionKey` that will be used for SDK globally /// /// This method will also encrypt this key and store in prefs @@ -124,7 +120,7 @@ class SessionManager with SdkAccessor { logger.e('Session key set to null, all paths will be removed'); prefs.remove(_userIdKeyPath); prefs.remove(_sessionKeyPath); - throw InvalidParameterError(); + return; } final userId = _userId; @@ -156,9 +152,15 @@ class SessionManager with SdkAccessor { } // Updates session and notify - Future updateSession() async { + Future updateSession() async { + // add completion + sdk.eventManager.notifySessionExpired(); + + final completer = Completer(); + sessionUpdateCompleters.add(completer); + if (isRefreshingKey) { - return false; + return completer.future; } final appId = sdk.state.appId; @@ -172,6 +174,7 @@ class SessionManager with SdkAccessor { isRefreshingKey = true; logger.i('Updating session with $_accessToken'); + try { final res = await sdk.api.send(AppSessionKeyUpdateRequest( appId: appId, @@ -181,16 +184,23 @@ class SessionManager with SdkAccessor { isRefreshingKey = false; logger.i('Updated session $res'); _applyRefreshedSessionKey(res); - return true; + return completer.future; } on SBError catch (err) { - logger.w('Failed to update session $err'); + logger.w('Failed to update session sb $err'); isRefreshingKey = false; if (err.code == ErrorCode.accessTokenNotValid) { sdk.eventManager.notifySessionTokenRequired(); } else { + flushResultCompleters(SessionKeyRefreshFailedError()); sdk.eventManager.notifySessionError(SessionKeyRefreshFailedError()); } - return false; + return completer.future; + } catch (err) { + logger.w('Failed to update session $err'); + isRefreshingKey = false; + flushResultCompleters(SessionKeyRefreshFailedError()); + sdk.eventManager.notifySessionError(SessionKeyRefreshFailedError()); + return completer.future; } } @@ -202,10 +212,8 @@ class SessionManager with SdkAccessor { setSessionKey(payload['new_key']); } - if (payload['expires_at'] != null) { - setSessionExpiresAt(payload['expires_at']); - } - + flushResultCompleters(SessionKeyRefreshSucceededError()); + eventManager.notifySessionError(SessionKeyRefreshSucceededError()); eventManager.notifySessionRefreshed(); sdk.reconnect(reset: true); } @@ -214,11 +222,20 @@ class SessionManager with SdkAccessor { Future _sessionSuccessHandler(String token) async { setAccessToken(token); - if (token.isEmpty) { - await updateSession(); - } else { - final error = InvalidAccessTokenError(); - sdk.eventManager.notifySessionError(error); + final hasSessionHandler = + sdk.eventManager.getHandler() != null; + + try { + final res = await sdk.api.send(AppSessionKeyUpdateRequest( + appId: sdk.state.appId ?? '', + accessToken: token, + expiringSession: hasSessionHandler, + )); + logger.i('Updated session $res'); + _applyRefreshedSessionKey(res); + } catch (error) { + sdk.eventManager.notifySessionError(InvalidAccessTokenError()); + flushResultCompleters(SessionKeyRefreshFailedError()); } } @@ -227,10 +244,22 @@ class SessionManager with SdkAccessor { sdk.eventManager.notifySessionError(error); } + void flushResultCompleters(SBError error) { + logger.v('Flush result with $error'); + sessionUpdateCompleters.forEach((e) { + e.completeError(error); + }); + sessionUpdateCompleters = []; + } + // Resets session manager void cleanUp() { - _sessionExpiresAt = 0; _eKey = null; _sessionKey = null; + isRefreshingKey = false; + sessionUpdateCompleters.forEach((e) { + e.completeError(SBError()); + }); + sessionUpdateCompleters = []; } } diff --git a/lib/params/group_channel_params.dart b/lib/params/group_channel_params.dart index 2f2a96c7..fe232cb0 100644 --- a/lib/params/group_channel_params.dart +++ b/lib/params/group_channel_params.dart @@ -62,7 +62,7 @@ class GroupChannelParams { /// List of user id who are operator @JsonKey(name: 'operator_ids') - List operatorUserIds = []; + List? operatorUserIds; //TBD //Int messageSurvivalSeconds; diff --git a/lib/query/user_list/banned_user_list_query.dart b/lib/query/user_list/banned_user_list_query.dart new file mode 100644 index 00000000..fda81548 --- /dev/null +++ b/lib/query/user_list/banned_user_list_query.dart @@ -0,0 +1,44 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_sdk/constant/enums.dart'; +import 'package:sendbird_sdk/core/models/error.dart'; +import 'package:sendbird_sdk/core/models/responses.dart'; +import 'package:sendbird_sdk/core/models/restricted_user.dart'; +import 'package:sendbird_sdk/query/base_query.dart'; +import 'package:sendbird_sdk/request/user/list/banned_user_list_request.dart'; +import 'package:sendbird_sdk/sdk/sendbird_sdk_api.dart'; + +@JsonSerializable() +class BannedUserListQuery extends QueryBase { + /// Channel type related to this query + ChannelType channelType; + + /// Channel url related to this query + String channelUrl; + + BannedUserListQuery({ + required this.channelType, + required this.channelUrl, + }); + + @override + Future> loadNext() async { + if (loading) throw QueryInProgressError(); + if (!hasNext) return []; + + loading = true; + + final req = BannedUserListRequest( + channelType: channelType, + channelUrl: channelUrl, + token: token, + limit: limit, + ); + + final sdk = SendbirdSdk().getInternal(); + final res = await sdk.api.send>(req); + loading = false; + token = res.next; + hasNext = res.next != ''; + return res.users; + } +} diff --git a/lib/query/user_list/blocked_user_list_query.dart b/lib/query/user_list/blocked_user_list_query.dart new file mode 100644 index 00000000..2743a5f4 --- /dev/null +++ b/lib/query/user_list/blocked_user_list_query.dart @@ -0,0 +1,33 @@ +import 'package:sendbird_sdk/core/models/error.dart'; +import 'package:sendbird_sdk/core/models/responses.dart'; +import 'package:sendbird_sdk/core/models/user.dart'; +import 'package:sendbird_sdk/query/base_query.dart'; +import 'package:sendbird_sdk/request/user/list/blocked_user_list_request.dart'; +import 'package:sendbird_sdk/sdk/sendbird_sdk_api.dart'; + +class BlockedUserListQuery extends QueryBase { + List userIds = []; + + BlockedUserListQuery({this.userIds = const []}); + + @override + Future> loadNext() async { + if (loading) throw QueryInProgressError(); + if (!hasNext) return []; + + loading = true; + + final req = BlockedUserListRequest( + token: token, + limit: limit, + userIds: userIds, + ); + + final sdk = SendbirdSdk().getInternal(); + final res = await sdk.api.send>(req); + loading = false; + token = res.next; + hasNext = res.next != ''; + return res.users; + } +} diff --git a/lib/query/user_list/muted_user_list_query.dart b/lib/query/user_list/muted_user_list_query.dart new file mode 100644 index 00000000..7f9e6ec0 --- /dev/null +++ b/lib/query/user_list/muted_user_list_query.dart @@ -0,0 +1,44 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:sendbird_sdk/constant/enums.dart'; +import 'package:sendbird_sdk/core/models/error.dart'; +import 'package:sendbird_sdk/core/models/responses.dart'; +import 'package:sendbird_sdk/core/models/restricted_user.dart'; +import 'package:sendbird_sdk/query/base_query.dart'; +import 'package:sendbird_sdk/request/user/list/banned_user_list_request.dart'; +import 'package:sendbird_sdk/sdk/sendbird_sdk_api.dart'; + +@JsonSerializable() +class MutedUserListQuery extends QueryBase { + /// Channel type related to this query + ChannelType channelType; + + /// Channel url related to this query + String channelUrl; + + MutedUserListQuery({ + required this.channelType, + required this.channelUrl, + }); + + @override + Future> loadNext() async { + if (loading) throw QueryInProgressError(); + if (!hasNext) return []; + + loading = true; + + final req = BannedUserListRequest( + channelType: channelType, + channelUrl: channelUrl, + token: token, + limit: limit, + ); + + final sdk = SendbirdSdk().getInternal(); + final res = await sdk.api.send>(req); + loading = false; + token = res.next; + hasNext = res.next != ''; + return res.users; + } +} diff --git a/lib/query/user_list/participant_list_query.dart b/lib/query/user_list/participant_list_query.dart new file mode 100644 index 00000000..e1ce4548 --- /dev/null +++ b/lib/query/user_list/participant_list_query.dart @@ -0,0 +1,33 @@ +import 'package:sendbird_sdk/core/models/error.dart'; +import 'package:sendbird_sdk/core/models/responses.dart'; +import 'package:sendbird_sdk/core/models/user.dart'; +import 'package:sendbird_sdk/query/base_query.dart'; +import 'package:sendbird_sdk/request/user/list/participant_list_request.dart'; +import 'package:sendbird_sdk/sdk/sendbird_sdk_api.dart'; + +class ParticipantListQuery extends QueryBase { + String channelUrl; + + ParticipantListQuery({required this.channelUrl}); + + @override + Future> loadNext() async { + if (loading) throw QueryInProgressError(); + if (!hasNext) return []; + + loading = true; + + final req = OpenChannelParticipantListRequest( + channelUrl: channelUrl, + token: token, + limit: limit, + ); + + final sdk = SendbirdSdk().getInternal(); + final res = await sdk.api.send>(req); + loading = false; + token = res.next; + hasNext = res.next != ''; + return res.users; + } +} diff --git a/lib/request/user/list/banned_user_list_request.dart b/lib/request/user/list/banned_user_list_request.dart index 8e670d25..e4a6f748 100644 --- a/lib/request/user/list/banned_user_list_request.dart +++ b/lib/request/user/list/banned_user_list_request.dart @@ -1,4 +1,5 @@ import 'package:sendbird_sdk/constant/enums.dart'; +import 'package:sendbird_sdk/core/models/restricted_user.dart'; import 'package:sendbird_sdk/request/abstract/api_request.dart'; import 'package:sendbird_sdk/core/models/responses.dart'; import 'package:sendbird_sdk/services/network/http_client.dart'; @@ -22,9 +23,12 @@ class BannedUserListRequest extends ApiRequest { @override Future response(res) async { - final users = - List.from(res['banned_list']).map((e) => e['user']).toList(); + final users = List.from(res['banned_list']); + users.forEach((e) { + e['type'] = 'banned'; + }); + final output = {'users': users, 'next': res['next']}; - return UserListQueryResponse.fromJson(output); + return UserListQueryResponse.fromJson(output); } } diff --git a/lib/sdk/internal/sendbird_sdk_internal.dart b/lib/sdk/internal/sendbird_sdk_internal.dart index f076b9fe..db61c767 100644 --- a/lib/sdk/internal/sendbird_sdk_internal.dart +++ b/lib/sdk/internal/sendbird_sdk_internal.dart @@ -24,7 +24,7 @@ import 'package:sendbird_sdk/utils/async/async_queue.dart'; import 'package:sendbird_sdk/utils/logger.dart'; import 'package:sendbird_sdk/utils/parsers.dart'; -const sdk_version = '3.1.5'; +const sdk_version = '3.1.6'; const platform = 'flutter'; /// Internal implementation for main class. Do not directly access this class. @@ -37,7 +37,7 @@ class SendbirdSdkInternal with WidgetsBindingObserver { CommandManager _cmdManager = CommandManager(); StreamManager _streamManager = StreamManager(); - ApiClient _api = ApiClient(); + late ApiClient _api; WebSocketClient? _webSocket; Completer? _loginCompleter; @@ -72,6 +72,7 @@ class SendbirdSdkInternal with WidgetsBindingObserver { ..token = apiToken; _options = options ?? Options(); + _api = ApiClient(state: _state, appId: appId, token: apiToken); _listenConnectionEvents(); } diff --git a/lib/services/network/api_client.dart b/lib/services/network/api_client.dart index b4cdbe61..dfe178e2 100644 --- a/lib/services/network/api_client.dart +++ b/lib/services/network/api_client.dart @@ -1,8 +1,9 @@ +import 'package:sendbird_sdk/core/models/state.dart'; import 'package:sendbird_sdk/request/abstract/api_request.dart'; import 'package:sendbird_sdk/services/network/http_client.dart'; class ApiClient { - HttpClient client = HttpClient(); + late HttpClient client; String? currentUserId; //inject userid whenever current user status changes String? appId; @@ -11,23 +12,26 @@ class ApiClient { String? token; int uploadSizeLimit = 30; + SendbirdState? state; + + ApiClient({this.state, this.appId, this.token}) { + client = HttpClient(state); + } + void initialize({ String? appId, - String? sessionKey, String? token, String? baseUrl, int? uploadSizeLimit, Map? headers, }) { if (appId != null) this.appId = appId; - if (sessionKey != null) this.sessionKey = sessionKey; if (token != null) this.token = token; if (baseUrl != null) this.baseUrl = baseUrl; if (uploadSizeLimit != null) this.uploadSizeLimit = uploadSizeLimit; client ..appId = appId ?? this.appId - ..sessionKey = sessionKey ?? this.sessionKey ..token = token ?? this.token ..baseUrl = baseUrl ?? this.baseUrl ..headers = headers ?? {}; diff --git a/lib/services/network/http_client.dart b/lib/services/network/http_client.dart index 7346c234..c801981e 100644 --- a/lib/services/network/http_client.dart +++ b/lib/services/network/http_client.dart @@ -7,6 +7,7 @@ import 'package:sendbird_sdk/constant/error_code.dart'; import 'package:sendbird_sdk/constant/types.dart'; import 'package:sendbird_sdk/core/models/error.dart'; import 'package:sendbird_sdk/core/models/file_info.dart'; +import 'package:sendbird_sdk/core/models/state.dart'; import 'package:sendbird_sdk/sdk/sendbird_sdk_api.dart'; import 'package:sendbird_sdk/utils/logger.dart'; import 'package:sendbird_sdk/utils/extensions.dart'; @@ -24,7 +25,6 @@ class HttpClient { String? baseUrl; String? appId; - String? sessionKey; String? token; bool isLocal = false; @@ -37,16 +37,10 @@ class HttpClient { MultipartRequest? request; - HttpClient({ - this.baseUrl, - this.appId, - this.sessionKey, - this.token, - this.headers = const {}, - }); + SendbirdState? state; + HttpClient(this.state); void cleanUp() { - sessionKey = null; token = null; headers = {}; uploadRequests = {}; @@ -55,11 +49,12 @@ class HttpClient { //form commom headers Map commonHeaders() { + final sessionKey = state?.sessionKey; final commonHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json', if (sessionKey != null) - 'Session-Key': sessionKey! + 'Session-Key': sessionKey else if (token != null) 'Api-Token': token! }; @@ -288,13 +283,14 @@ class HttpClient { errorController.sink.add(err); //NOTE: is this best way to do?.. if (err.code == ErrorCode.sessionKeyExpired) { - sessionKey = null; - final result = - await SendbirdSdk().getInternal().sessionManager.updateSession(); - if (result) { - throw SessionKeyRefreshSucceededError(); - } else { - throw SessionKeyRefreshFailedError(); + log('Attempting to update session due to expired'); + // TODO: refactor + SendbirdSdk().getInternal().state.sessionKey = null; + SendbirdSdk().getInternal().sessionManager.setSessionKey(null); + try { + await SendbirdSdk().getInternal().sessionManager.updateSession(); + } catch (e) { + rethrow; } } } @@ -303,7 +299,8 @@ class HttpClient { case 200: return res; case 400: - logger.e('Bad request: ${res['message']}'); + logger.e( + 'Bad request: ${res['message']} for ${response.request!.url.toString()}'); throw BadRequestError(message: res['message'], code: res['code']); case 401: case 403: diff --git a/pubspec.lock b/pubspec.lock index d11375e9..f55d54b4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -324,6 +324,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + intl: + dependency: "direct dev" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" io: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1e583983..cc7dae95 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: sendbird_sdk description: The Flutter SDK for Sendbird Chat brings modern messenger chat features to your iOS and Android deployments -version: 3.1.5 +version: 3.1.6 homepage: https://www.sendbird.com repository: https://www.github.com/sendbird/sendbird-sdk-flutter documentation: https://sendbird.com/docs/chat/v3/flutter/quickstart/send-first-message @@ -33,3 +33,4 @@ dev_dependencies: build_runner: ^1.0.0 stack_trace: ^1.9.6 pedantic: ^1.0.0 + intl: