From 0a147bfc25218d99cda01ae10c2dd698eedca3d0 Mon Sep 17 00:00:00 2001 From: Gbogboade Ayomide Date: Fri, 19 Jan 2024 00:13:51 +0100 Subject: [PATCH 1/2] feat: add space highlevel in sdk --- .../lib/views/group/add_group_member.dart | 2 +- .../lib/views/group/group_members_dialog.dart | 25 +- examples/use_cases/lib/chats/chat.dart | 4 +- lib/src/chat/src/models/group_access.dart | 25 ++ lib/src/chat/src/models/group_info_dto.dart | 6 + lib/src/chat/src/update_group_config.dart | 4 +- lib/src/helpers/src/converters.dart | 25 +- lib/src/models/src/all.dart | 40 ++ lib/src/pushapi/pushapi.dart | 4 +- lib/src/pushapi/src/chat.dart | 30 +- lib/src/pushapi/src/{ => models}/models.dart | 18 +- lib/src/pushapi/src/models/spaces_models.dart | 96 +++++ lib/src/pushapi/src/space.dart | 358 ++++++++++++++++++ lib/src/pushstream/src/data_modifier.dart | 4 +- lib/src/spaces/space.dart | 1 + lib/src/spaces/src/add_listeners.dart | 4 +- lib/src/spaces/src/add_speakers.dart | 4 +- lib/src/spaces/src/create_v2.dart | 53 +++ lib/src/spaces/src/remove_listeners.dart | 4 +- lib/src/spaces/src/remove_speakers.dart | 4 +- 20 files changed, 641 insertions(+), 70 deletions(-) rename lib/src/pushapi/src/{ => models}/models.dart (85%) create mode 100644 lib/src/pushapi/src/models/spaces_models.dart create mode 100644 lib/src/pushapi/src/space.dart create mode 100644 lib/src/spaces/src/create_v2.dart diff --git a/examples/demo_app/lib/views/group/add_group_member.dart b/examples/demo_app/lib/views/group/add_group_member.dart index 2c5b079..20e6ec2 100644 --- a/examples/demo_app/lib/views/group/add_group_member.dart +++ b/examples/demo_app/lib/views/group/add_group_member.dart @@ -70,7 +70,7 @@ class _AddGroupMemberState extends ConsumerState { showLoadingDialog(); result = await pushUser.chat.group.add( chatId: widget.chatId!, - role: widget.isAdmin ? 'ADMIN' : 'MEMBER', + role: widget.isAdmin ? GroupRoles.ADMIN : GroupRoles.MEMBER, accounts: [address], ); diff --git a/examples/demo_app/lib/views/group/group_members_dialog.dart b/examples/demo_app/lib/views/group/group_members_dialog.dart index c4efb7d..8104f6f 100644 --- a/examples/demo_app/lib/views/group/group_members_dialog.dart +++ b/examples/demo_app/lib/views/group/group_members_dialog.dart @@ -329,7 +329,7 @@ class _MemberActionWidgetState extends ConsumerState { showLoadingDialog(); pushUser.chat.group.remove( chatId: widget.chatId!, - role: 'MEMBER', + role: GroupRoles.MEMBER, accounts: [widget.item.address], ).then((value) { pop(); @@ -350,7 +350,7 @@ class _MemberActionWidgetState extends ConsumerState { pushUser.chat.group.remove( chatId: widget.chatId!, - role: 'ADMIN', + role: GroupRoles.ADMIN, accounts: [widget.item.address], ).then((value) { pop(); @@ -370,7 +370,7 @@ class _MemberActionWidgetState extends ConsumerState { showLoadingDialog(); pushUser.chat.group.modify( chatId: widget.chatId!, - role: 'MEMBER', + role: GroupRoles.MEMBER, accounts: [widget.item.address], ).then((value) { pop(); @@ -390,7 +390,7 @@ class _MemberActionWidgetState extends ConsumerState { showLoadingDialog(); pushUser.chat.group.modify( chatId: widget.chatId!, - role: 'ADMIN', + role: GroupRoles.ADMIN, accounts: [widget.item.address], ).then((value) { pop(); @@ -404,23 +404,6 @@ class _MemberActionWidgetState extends ConsumerState { : 'Member promoted successfully', ); }); - // removeMembers( - // chatId: widget.chatId!, - // members: [widget.item.address], - // ).then((value) { - // addAdmins(chatId: widget.chatId!, admins: [widget.item.address]) - // .then((value) { - // ref.read(chatRoomProvider).getLatestGroupMembers(); - // pop(); - // showMyDialog( - // context: context, - // title: 'Remove User', - // message: value == null - // ? 'Cannot promote user' - // : 'Member promoted successfully', - // ); - // }); - // }); } } diff --git a/examples/use_cases/lib/chats/chat.dart b/examples/use_cases/lib/chats/chat.dart index 9b365a4..b57061f 100644 --- a/examples/use_cases/lib/chats/chat.dart +++ b/examples/use_cases/lib/chats/chat.dart @@ -183,7 +183,7 @@ Future runChatClassUseCases() async { log('PushAPI.group.add'); final addMember = await userAlice.chat.group.add( chatId: groupChatId, - role: 'MEMBER', + role: GroupRoles.MEMBER, accounts: [wallet3Signer.getAddress()], ); log(addMember); @@ -194,7 +194,7 @@ Future runChatClassUseCases() async { log('PushAPI.group.remove'); final removeMember = await userAlice.chat.group.remove( chatId: groupChatId, - role: 'MEMBER', + role: GroupRoles.MEMBER, accounts: [wallet3Signer.getAddress()], ); log(removeMember); diff --git a/lib/src/chat/src/models/group_access.dart b/lib/src/chat/src/models/group_access.dart index 89cab0a..5c325aa 100644 --- a/lib/src/chat/src/models/group_access.dart +++ b/lib/src/chat/src/models/group_access.dart @@ -25,3 +25,28 @@ class GroupAccess { return data; } } + +class SpaceAccess { + bool entry; + bool chat; + + SpaceAccess({ + required this.entry, + required this.chat, + }); + + factory SpaceAccess.fromJson(Map json) { + return SpaceAccess( + entry: json['entry'] ?? false, + chat: json['chat'] ?? false, + ); + } + + Map toJson() { + final Map data = { + 'entry': entry, + 'chat': chat, + }; + return data; + } +} diff --git a/lib/src/chat/src/models/group_info_dto.dart b/lib/src/chat/src/models/group_info_dto.dart index 4f0ec47..63bc427 100644 --- a/lib/src/chat/src/models/group_info_dto.dart +++ b/lib/src/chat/src/models/group_info_dto.dart @@ -80,3 +80,9 @@ class GroupInfoDTO { return GroupInfoDTO.fromJson(group.toJson()); } } + +extension GroupExtension on GroupInfoDTO? { + SpaceInfoDTO get toSpaceInfo { + return groupInfoDtoToSpaceInfoDto(this!); + } +} diff --git a/lib/src/chat/src/update_group_config.dart b/lib/src/chat/src/update_group_config.dart index 1b9ac17..7b03d2f 100644 --- a/lib/src/chat/src/update_group_config.dart +++ b/lib/src/chat/src/update_group_config.dart @@ -22,7 +22,7 @@ class ChatUpdateConfigProfileType { }); } -Future updateGroupConfig({ +Future updateGroupConfig({ required ChatUpdateConfigProfileType options, }) async { options.account ??= getCachedWallet()?.address; @@ -76,5 +76,5 @@ Future updateGroupConfig({ throw Exception(result); } - return GroupDTO.fromJson(result); + return GroupInfoDTO.fromJson(result); } diff --git a/lib/src/helpers/src/converters.dart b/lib/src/helpers/src/converters.dart index bcc9224..eaacafb 100644 --- a/lib/src/helpers/src/converters.dart +++ b/lib/src/helpers/src/converters.dart @@ -61,14 +61,8 @@ SpaceDTO groupDtoToSpaceDto(GroupDTO groupDto) { ); } -SpaceDTO groupInfoDtoToSpaceDto(GroupInfoDTO groupDto) { - return SpaceDTO( - members: [], - pendingMembers: [], - contractAddressERC20: null, - numberOfERC20: -1, - numberOfNFTTokens: -1, - verificationProof: '', +SpaceInfoDTO groupInfoDtoToSpaceInfoDto(GroupInfoDTO groupDto) { + return SpaceInfoDTO( spaceImage: groupDto.groupImage, spaceName: groupDto.groupName, isPublic: groupDto.isPublic, @@ -82,6 +76,21 @@ SpaceDTO groupInfoDtoToSpaceDto(GroupInfoDTO groupDto) { ); } +SpaceInfoDTO spaceDtoToSpaceInfoDto(SpaceDTO groupDto) { + return SpaceInfoDTO( + spaceImage: groupDto.spaceImage, + spaceName: groupDto.spaceName, + isPublic: groupDto.isPublic, + spaceDescription: groupDto.spaceDescription ?? '', + spaceCreator: groupDto.spaceCreator, + spaceId: groupDto.spaceId, + scheduleAt: groupDto.scheduleAt, + scheduleEnd: groupDto.scheduleEnd, + status: groupDto.status, + meta: groupDto.meta, + ); +} + List convertToWalletAddressList(List memberList) { return memberList.map((member) => member.wallet).toList(); } diff --git a/lib/src/models/src/all.dart b/lib/src/models/src/all.dart index a53e1b7..2da8eef 100644 --- a/lib/src/models/src/all.dart +++ b/lib/src/models/src/all.dart @@ -410,6 +410,46 @@ class SpaceDTO { : null; } +extension SpaceDTOExtension on SpaceDTO { + SpaceInfoDTO get toSpaceInfo { + return spaceDtoToSpaceInfoDto(this); + } +} + +class SpaceInfoDTO { + final String spaceName; + final String? spaceImage; + final String spaceDescription; + final bool isPublic; + final String spaceCreator; + final String spaceId; + final DateTime? scheduleAt; + final DateTime? scheduleEnd; + final ChatStatus? status; + final dynamic rules; + final String? meta; + final String? sessionKey; + final String? encryptedSecret; + final Map? inviteeDetails; + + SpaceInfoDTO({ + required this.spaceName, + this.spaceImage, + required this.spaceDescription, + required this.isPublic, + required this.spaceCreator, + required this.spaceId, + this.scheduleAt, + this.scheduleEnd, + this.status, + this.rules, + this.meta, + this.sessionKey, + this.encryptedSecret, + this.inviteeDetails, + }); +} + class SpaceMemberDTO { String wallet; String publicKey; diff --git a/lib/src/pushapi/pushapi.dart b/lib/src/pushapi/pushapi.dart index 5309264..14b039e 100644 --- a/lib/src/pushapi/pushapi.dart +++ b/lib/src/pushapi/pushapi.dart @@ -1,4 +1,6 @@ -export 'src/models.dart'; +export 'src/models/models.dart'; +export 'src/models/spaces_models.dart'; export 'src/pushapi.dart'; export 'src/chat.dart' hide GroupAPI, GroupParticipantsAPI; export 'src/user.dart'; +export 'src/space.dart'; diff --git a/lib/src/pushapi/src/chat.dart b/lib/src/pushapi/src/chat.dart index dc9d90d..2e4c792 100644 --- a/lib/src/pushapi/src/chat.dart +++ b/lib/src/pushapi/src/chat.dart @@ -282,7 +282,7 @@ class GroupAPI { return PUSH_CHAT.getGroupInfo(chatId: chatId); } - Future update({ + Future update({ required String chatId, required GroupUpdateOptions options, }) async { @@ -320,21 +320,15 @@ class GroupAPI { ); } - ///role: 'ADMIN' | 'MEMBER'; Future add({ required String chatId, - required String role, + required GroupRoles role, required List accounts, }) async { if (!_hasSigner) { throw Exception(PushAPI.ensureSignerMessage()); } - final validRoles = ['ADMIN', 'MEMBER']; - if (!validRoles.contains(role)) { - throw Exception('Invalid role provided.'); - } - if (accounts.isEmpty) { throw Exception('accounts array cannot be empty!'); } @@ -345,7 +339,7 @@ class GroupAPI { } } - if (role == 'ADMIN') { + if (role == GroupRoles.ADMIN) { return PUSH_CHAT.addAdmins( chatId: chatId, admins: accounts, @@ -364,21 +358,15 @@ class GroupAPI { } } - ///role: 'ADMIN' | 'MEMBER'; Future remove({ required String chatId, - required String role, + required GroupRoles role, required List accounts, }) async { if (!_hasSigner) { throw Exception(PushAPI.ensureSignerMessage()); } - final validRoles = ['ADMIN', 'MEMBER']; - if (!validRoles.contains(role)) { - throw Exception('Invalid role provided.'); - } - if (accounts.isEmpty) { throw Exception('accounts array cannot be empty!'); } @@ -425,21 +413,15 @@ class GroupAPI { return info(chatId: chatId); } - ///role: 'ADMIN' | 'MEMBER'; Future modify({ required String chatId, - required String role, + required GroupRoles role, required List accounts, }) async { if (!_hasSigner) { throw Exception(PushAPI.ensureSignerMessage()); } - final validRoles = ['ADMIN', 'MEMBER']; - if (!validRoles.contains(role)) { - throw Exception('Invalid role provided.'); - } - if (accounts.isEmpty) { throw Exception('accounts array cannot be empty!'); } @@ -453,7 +435,7 @@ class GroupAPI { return PUSH_CHAT.modifyRoles( options: ModifyRolesType( chatId: chatId, - newRole: role, + newRole: role.value, account: _account, members: accounts, pgpPrivateKey: _decryptedPgpPvtKey, diff --git a/lib/src/pushapi/src/models.dart b/lib/src/pushapi/src/models/models.dart similarity index 85% rename from lib/src/pushapi/src/models.dart rename to lib/src/pushapi/src/models/models.dart index 4f8578d..64be060 100644 --- a/lib/src/pushapi/src/models.dart +++ b/lib/src/pushapi/src/models/models.dart @@ -1,6 +1,6 @@ // ignore_for_file: constant_identifier_names -import '../../../push_restapi_dart.dart'; +import '../../../../push_restapi_dart.dart'; class PushAPIInitializeOptions { void Function(ProgressHookType)? progressHook; @@ -111,3 +111,19 @@ class GroupUpdateOptions { this.rules, }); } + +enum GroupRoles { MEMBER, ADMIN } + +extension GroupRolesExtension on GroupRoles { + String get value { + switch (this) { + case GroupRoles.ADMIN: + return 'ADMIN'; + case GroupRoles.MEMBER: + return 'MEMBER'; + + default: + return 'MEMBER'; + } + } +} diff --git a/lib/src/pushapi/src/models/spaces_models.dart b/lib/src/pushapi/src/models/spaces_models.dart new file mode 100644 index 0000000..1ad336b --- /dev/null +++ b/lib/src/pushapi/src/models/spaces_models.dart @@ -0,0 +1,96 @@ +// ignore_for_file: constant_identifier_names + +import '../../../../push_restapi_dart.dart'; + +class SpaceSchedule { + final DateTime start; + final DateTime? end; + + SpaceSchedule({ + required this.start, + this.end, + }); +} + +class SpaceUpdateOptions { + String? name; + String? description; + String? image; + DateTime? scheduleAt; + DateTime? scheduleEnd; + ChatStatus? status; + String? meta; + dynamic rules; + + SpaceUpdateOptions({ + this.name, + this.description, + this.image, + this.scheduleAt, + this.scheduleEnd, + this.status, + this.meta, + this.rules, + }); +} + +class SpaceCreationOptions { + final String description; + final String image; + late final SpaceParticipants participants; + final SpaceSchedule schedule; + final dynamic rules; + final bool private; + + SpaceCreationOptions({ + required this.description, + required this.image, + SpaceParticipants? participants, + required this.schedule, + this.rules, + required this.private, + }) { + this.participants = participants ?? SpaceParticipants(); + } +} + +class SpaceParticipants { + final List speakers; + final List listeners; + + SpaceParticipants({ + this.speakers = const [], + this.listeners = const [], + }); +} + +enum SpaceRoles { LISTENER, SPEAKERS } + +extension SpaceRolesExtension on SpaceRoles { + String get toGroupValue { + switch (this) { + case SpaceRoles.SPEAKERS: + return 'ADMIN'; + case SpaceRoles.LISTENER: + return 'MEMBER'; + + default: + return 'MEMBER'; + } + } +} + +enum SpaceListType { SPACES, REQUESTS } + +extension SpaceListTypeExtension on SpaceListType { + ChatListType get toChatListType { + switch (this) { + case SpaceListType.SPACES: + return ChatListType.CHATS; + case SpaceListType.REQUESTS: + return ChatListType.REQUESTS; + default: + throw Exception('Unsupported SpaceListType: $this'); + } + } +} diff --git a/lib/src/pushapi/src/space.dart b/lib/src/pushapi/src/space.dart new file mode 100644 index 0000000..dfab167 --- /dev/null +++ b/lib/src/pushapi/src/space.dart @@ -0,0 +1,358 @@ +// ignore_for_file: library_prefixes + +import 'package:push_restapi_dart/src/pushapi/src/chat.dart'; + +import '../../../push_restapi_dart.dart'; +import '../../spaces/space.dart' as PUSH_SPACE; +import '../../chat/chat.dart' as PUSH_CHAT; + +class Space { + late final Signer? _signer; + + late final String _account; + late final String? _decryptedPgpPvtKey; + void Function(ProgressHookType)? progressHook; + + late final Chat chatInstance; + late final GroupParticipantsAPI participants; + + Space({ + Signer? signer, + required String account, + String? decryptedPgpPvtKey, + String? pgpPublicKey, + required this.progressHook, + }) { + _signer = signer; + _account = account; + _decryptedPgpPvtKey = decryptedPgpPvtKey; + + chatInstance = Chat( + account: account, + progressHook: progressHook, + decryptedPgpPvtKey: _decryptedPgpPvtKey, + pgpPublicKey: pgpPublicKey, + signer: _signer); + + participants = GroupParticipantsAPI(); + } + + bool get _hasSigner => _signer != null; + + Future create({ + required String name, + required SpaceCreationOptions options, + }) async { + return PUSH_SPACE.createSpaceV2( + signer: _signer, + pgpPrivateKey: _decryptedPgpPvtKey, + spaceName: name, + spaceDescription: options.description, + listeners: options.participants.listeners, + speakers: options.participants.speakers, + spaceImage: options.image, + isPublic: !options.private, + rules: options.rules, + scheduleAt: options.schedule.start, + scheduleEnd: options.schedule.end, + account: _account, + ); + } + + Future update({ + required String spaceId, + required SpaceUpdateOptions options, + }) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + GroupInfoDTO? group; + + try { + group = await PUSH_CHAT.getGroupInfo(chatId: spaceId); + } catch (e) { + throw Exception('Space not found'); + } + final updateGroupProfileOptions = ChatUpdateGroupProfileType( + chatId: spaceId, + groupName: options.name ?? group.groupName, + groupDescription: options.description ?? group.groupDescription, + groupImage: options.image ?? group.groupImage, + rules: options.rules ?? group.rules, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + ); + + final updateGroupConfigOptions = ChatUpdateConfigProfileType( + chatId: spaceId, + meta: options.meta ?? group.meta, + scheduleAt: options.scheduleAt ?? group.scheduleAt, + scheduleEnd: options.scheduleEnd ?? group.scheduleEnd, + status: options.status ?? group.status, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + ); + + await PUSH_CHAT.updateGroupProfile(options: updateGroupProfileOptions); + final result = + await PUSH_CHAT.updateGroupConfig(options: updateGroupConfigOptions); + + return result.toSpaceInfo; + } + + Future info({required String spaceId}) async { + final result = await PUSH_CHAT.getGroupInfo(chatId: spaceId); + return result.toSpaceInfo; + } + + Future permissions({required String spaceId}) async { + final result = + await PUSH_CHAT.getGroupAccess(chatId: spaceId, did: _account); + + return SpaceAccess.fromJson(result.toJson()); + } + + Future add({ + required String spaceId, + required SpaceRoles role, + required List accounts, + }) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + if (accounts.isEmpty) { + throw Exception('accounts array cannot be empty!'); + } + + for (var account in accounts) { + if (!isValidETHAddress(account)) { + throw Exception('Invalid account address: $account'); + } + } + + if (role == SpaceRoles.SPEAKERS) { + return (await PUSH_CHAT.addAdmins( + chatId: spaceId, + admins: accounts, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + )) + .toSpaceInfo; + } else { + return (await PUSH_CHAT.addMembers( + chatId: spaceId, + members: accounts, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + )) + .toSpaceInfo; + } + } + + Future remove({ + required String spaceId, + required SpaceRoles role, + required List accounts, + }) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + if (accounts.isEmpty) { + throw Exception('accounts array cannot be empty!'); + } + + for (var account in accounts) { + if (!isValidETHAddress(account)) { + throw Exception('Invalid account address: $account'); + } + } + + var adminsToRemove = []; + var membersToRemove = []; + + for (var account in accounts) { + final status = + await PUSH_CHAT.getGroupMemberStatus(chatId: spaceId, did: account); + if (status.isAdmin) { + adminsToRemove.add(account); + } else { + membersToRemove.add(account); + } + } + + if (adminsToRemove.isNotEmpty) { + await PUSH_CHAT.removeAdmins( + chatId: spaceId, + admins: adminsToRemove, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ); + } + + if (membersToRemove.isNotEmpty) { + await PUSH_CHAT.removeMembers( + chatId: spaceId, + members: membersToRemove, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ); + } + + return info(spaceId: spaceId); + } + + Future modify({ + required String spaceId, + required SpaceRoles role, + required List accounts, + }) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + if (accounts.isEmpty) { + throw Exception('accounts array cannot be empty!'); + } + + for (var account in accounts) { + if (!isValidETHAddress(account)) { + throw Exception('Invalid account address: $account'); + } + } + + return (await PUSH_CHAT.modifyRoles( + options: ModifyRolesType( + chatId: spaceId, + newRole: role.toGroupValue, + account: _account, + members: accounts, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ), + )) + .toSpaceInfo; + } + + Future join({required String spaceId}) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + final status = await PUSH_CHAT.getGroupMemberStatus( + chatId: spaceId, + did: _account, + ); + + if (status.isPending) { + await PUSH_CHAT.approve( + senderAddress: spaceId, + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ); + } else if (!status.isMember) { + await PUSH_CHAT.addMembers( + chatId: spaceId, + members: [_account], + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ); + } + + return await info(spaceId: spaceId); + } + + Future leave({required String spaceId}) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + // chatInstance.group.leave(target: spaceId); + + final status = await PUSH_CHAT.getGroupMemberStatus( + chatId: spaceId, + did: _account, + ); + + if (status.isAdmin) { + await PUSH_CHAT.removeAdmins( + chatId: spaceId, + admins: [_account], + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ); + } else if (!status.isMember) { + await PUSH_CHAT.removeMembers( + chatId: spaceId, + members: [_account], + account: _account, + pgpPrivateKey: _decryptedPgpPvtKey, + signer: _signer, + ); + } + + return await info(spaceId: spaceId); + } + + Future> search({ + required String term, + int page = 1, + int limit = 20, + }) async { + final response = await PUSH_SPACE.searchSpaces( + searchTerm: term, + pageNumber: page, + pageSize: limit, + ); + + return response.map((e) => e.toSpaceInfo).toList(); + } + + Future?> trending({ + required String term, + int page = 1, + int limit = 20, + }) async { + return await PUSH_SPACE.trending(page: page, limit: limit); + } + + Future?> list({ + required SpaceListType type, + int page = 1, + int limit = 20, + String? overrideAccount, + }) { + return PUSH_SPACE.spaces( + accountAddress: _account, + limit: limit, + page: page, + pgpPrivateKey: _decryptedPgpPvtKey, + toDecrypt: _hasSigner, + ); + } + + Future accept({required String spaceId}) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + return chatInstance.accept(target: spaceId); + } + + Future reject({required String spaceId}) async { + if (!_hasSigner) { + throw Exception(PushAPI.ensureSignerMessage()); + } + + return chatInstance.reject(target: spaceId); + } +} diff --git a/lib/src/pushstream/src/data_modifier.dart b/lib/src/pushstream/src/data_modifier.dart index ef13a8f..594c52b 100644 --- a/lib/src/pushstream/src/data_modifier.dart +++ b/lib/src/pushstream/src/data_modifier.dart @@ -146,10 +146,10 @@ class DataModifier { 'event': eventType, 'origin': data['messageOrigin'], 'timestamp': data['timestamp'].toString(), - 'chatId': data['chatId'], + 'chatId': data['chatId'], 'from': data['fromCAIP10'], 'to': [ - if (data['toCAIP10'] != null) data['toCAIP10'] + if (data['toCAIP10'] != null) data['toCAIP10'], ], 'message': { 'type': data['messageType'], diff --git a/lib/src/spaces/space.dart b/lib/src/spaces/space.dart index 54eed84..f04fd48 100644 --- a/lib/src/spaces/space.dart +++ b/lib/src/spaces/space.dart @@ -24,3 +24,4 @@ export 'src/helpers/get_latest_live_space_data.dart'; export 'src/helpers/get_incoming_index_from_address.dart'; export 'src/helpers/set_hand_raised_for_listener.dart'; export 'src/helpers/is_host.dart'; +export 'src/create_v2.dart'; diff --git a/lib/src/spaces/src/add_listeners.dart b/lib/src/spaces/src/add_listeners.dart index b0fd535..0571365 100644 --- a/lib/src/spaces/src/add_listeners.dart +++ b/lib/src/spaces/src/add_listeners.dart @@ -1,6 +1,6 @@ import '../../../push_restapi_dart.dart'; -Future addListeners({ +Future addListeners({ required String spaceId, String? account, Signer? signer, @@ -33,7 +33,7 @@ Future addListeners({ account: account, pgpPrivateKey: pgpPrivateKey); if (group != null) { - return groupInfoDtoToSpaceDto(group); + return groupInfoDtoToSpaceInfoDto(group); } else { throw Exception('Error while updating Space : $spaceId'); } diff --git a/lib/src/spaces/src/add_speakers.dart b/lib/src/spaces/src/add_speakers.dart index f929051..a5890ac 100644 --- a/lib/src/spaces/src/add_speakers.dart +++ b/lib/src/spaces/src/add_speakers.dart @@ -1,6 +1,6 @@ import '../../../push_restapi_dart.dart'; -Future addSpeakers({ +Future addSpeakers({ required String spaceId, String? account, Signer? signer, @@ -31,7 +31,7 @@ Future addSpeakers({ account: account, pgpPrivateKey: pgpPrivateKey); if (group != null) { - return groupInfoDtoToSpaceDto(group); + return groupInfoDtoToSpaceInfoDto(group); } else { throw Exception('Error while updating Space : $spaceId'); } diff --git a/lib/src/spaces/src/create_v2.dart b/lib/src/spaces/src/create_v2.dart new file mode 100644 index 0000000..aeb0321 --- /dev/null +++ b/lib/src/spaces/src/create_v2.dart @@ -0,0 +1,53 @@ +import '../../../push_restapi_dart.dart'; + +Future createSpaceV2({ + String? account, + Signer? signer, + String? pgpPrivateKey, + required String spaceName, + required String spaceDescription, + String? spaceImage, + required List listeners, + required List speakers, + bool isPublic = true, + required DateTime scheduleAt, + DateTime? scheduleEnd, + dynamic rules, +}) async { + try { + final spaceRules = rules != null ? convertSpaceRulesToRules(rules) : null; + final result = await createGroupV2( + options: ChatCreateGroupTypeV2( + signer: signer, + account: account, + groupName: spaceName, + groupDescription: spaceDescription, + members: listeners, + admins: speakers, + groupImage: spaceImage, + isPublic: isPublic, + groupType: 'spaces', + pgpPrivateKey: pgpPrivateKey, + rules: spaceRules, + config: GroupConfig( + meta: null, + scheduleAt: scheduleAt, + scheduleEnd: scheduleEnd, + status: 'PENDING', + ), + ), + ); + + return result.toSpaceInfo; + } catch (e) { + log("[Push SDK] - API - Error - API createSpace -: $e "); + rethrow; + } +} + +dynamic convertSpaceRulesToRules(dynamic spaceRules) { + return { + "entry": spaceRules['entry'], + "chat": null, + }; +} diff --git a/lib/src/spaces/src/remove_listeners.dart b/lib/src/spaces/src/remove_listeners.dart index 2938097..566d7d4 100644 --- a/lib/src/spaces/src/remove_listeners.dart +++ b/lib/src/spaces/src/remove_listeners.dart @@ -1,6 +1,6 @@ import '../../../push_restapi_dart.dart'; -Future removeListeners({ +Future removeListeners({ required String spaceId, String? account, Signer? signer, @@ -32,7 +32,7 @@ Future removeListeners({ account: account, pgpPrivateKey: pgpPrivateKey); if (group != null) { - return groupInfoDtoToSpaceDto(group); + return groupInfoDtoToSpaceInfoDto(group); } else { throw Exception('Error while updating Space : $spaceId'); } diff --git a/lib/src/spaces/src/remove_speakers.dart b/lib/src/spaces/src/remove_speakers.dart index 3fb4aef..3f67c45 100644 --- a/lib/src/spaces/src/remove_speakers.dart +++ b/lib/src/spaces/src/remove_speakers.dart @@ -1,6 +1,6 @@ import '../../../push_restapi_dart.dart'; -Future removeSpeakers({ +Future removeSpeakers({ required String spaceId, String? account, Signer? signer, @@ -32,7 +32,7 @@ Future removeSpeakers({ account: account, pgpPrivateKey: pgpPrivateKey); if (group != null) { - return groupInfoDtoToSpaceDto(group); + return groupInfoDtoToSpaceInfoDto(group); } else { throw Exception('Error while updating Space : $spaceId'); } From 48d4bc8aa2b1e887b5f216712bb7d4f62a51398a Mon Sep 17 00:00:00 2001 From: Gbogboade Ayomide Date: Thu, 25 Jan 2024 10:52:52 +0100 Subject: [PATCH 2/2] feat: add spaces to push stream --- lib/src/pushapi/src/pushapi.dart | 8 + lib/src/pushapi/src/space.dart | 4 + lib/src/pushstream/src/data_modifier.dart | 255 ++++++++++++++++++++++ lib/src/pushstream/src/models/models.dart | 36 ++- lib/src/pushstream/src/push_stream.dart | 63 ++++++ 5 files changed, 365 insertions(+), 1 deletion(-) diff --git a/lib/src/pushapi/src/pushapi.dart b/lib/src/pushapi/src/pushapi.dart index c99f1df..8977728 100644 --- a/lib/src/pushapi/src/pushapi.dart +++ b/lib/src/pushapi/src/pushapi.dart @@ -14,6 +14,7 @@ class PushAPI { late Chat chat; late PushStream stream; + late Space space; PushAPI({ Signer? signer, required String account, @@ -40,6 +41,13 @@ class PushAPI { pgpPublicKey: pgpPublicKey, progressHook: progressHook, ); + space = Space( + signer: _signer, + account: _account, + decryptedPgpPvtKey: _decryptedPgpPvtKey, + pgpPublicKey: pgpPublicKey, + progressHook: progressHook, + ); } static Future initialize({ diff --git a/lib/src/pushapi/src/space.dart b/lib/src/pushapi/src/space.dart index dfab167..fa05c9d 100644 --- a/lib/src/pushapi/src/space.dart +++ b/lib/src/pushapi/src/space.dart @@ -355,4 +355,8 @@ class Space { return chatInstance.reject(target: spaceId); } + + + + } diff --git a/lib/src/pushstream/src/data_modifier.dart b/lib/src/pushstream/src/data_modifier.dart index 594c52b..f6f5df0 100644 --- a/lib/src/pushstream/src/data_modifier.dart +++ b/lib/src/pushstream/src/data_modifier.dart @@ -1,6 +1,33 @@ import '../../../push_restapi_dart.dart'; class DataModifier { + static String convertToProposedNameForSpace(String currentEventName) { + switch (currentEventName) { + case 'create': + return ProposedEventNames.CreateSpace; + case 'update': + return ProposedEventNames.UpdateSpace; + case 'request': + return ProposedEventNames.SpaceRequest; + case 'accept': + return ProposedEventNames.SpaceAccept; + case 'reject': + return ProposedEventNames.SpaceReject; + case 'leaveSpace': + return ProposedEventNames.LeaveSpace; + case 'joinSpace': + return ProposedEventNames.JoinSpace; + case 'remove': + return ProposedEventNames.SpaceRemove; + case 'start': + return ProposedEventNames.StartSpace; + case 'stop': + return ProposedEventNames.StopSpace; + default: + throw Exception('Unknown current event name: $currentEventName'); + } + } + static Future handleChatGroupEvent({ required dynamic data, bool includeRaw = false, @@ -142,6 +169,10 @@ class DataModifier { bool includeRaw, String eventType, ) { + + if (data['hasIntent']==false && eventType == 'message') { + eventType = MessageEventType.request; + } final messageEvent = { 'event': eventType, 'origin': data['messageOrigin'], @@ -327,4 +358,228 @@ class DataModifier { } return eventData; } + + static dynamic handleSpaceEvent({ + required dynamic data, + bool includeRaw = false, + }) { + // Check the eventType and map accordingly + switch (data.eventType) { + case 'create': + return mapToCreateSpaceEvent(data, includeRaw); + case 'update': + return mapToUpdateSpaceEvent(data, includeRaw); + case 'request': + return mapToRequestSpaceEvent(data, includeRaw); + case 'remove': + return mapToRemoveSpaceEvent(data, includeRaw); + case 'joinSpace': + return mapToJoinSpaceEvent(data, includeRaw); + case 'leaveSpace': + return mapToLeaveSpaceEvent(data, includeRaw); + case 'start': + return mapToStartSpaceEvent(data, includeRaw); + case 'stop': + return mapToStopSpaceEvent(data, includeRaw); + default: + // If the eventType is unknown, check for known messageCategories + switch (data['messageCategory']) { + case 'Approve': + return mapToSpaceApproveEvent(data, includeRaw); + + case 'Reject': + return mapToSpaceRejectEvent(data, includeRaw); + // Add other cases as needed for different message categories + default: + log('Unknown eventType or messageCategory for space: ${data['eventType']} ${data['messageCategory']}'); + return data; + } + } + } + + static dynamic mapToSpaceRejectEvent(data, bool includeRaw) { + final baseEventData = { + 'event': 'reject', + 'origin': data.messageOrigin == 'other' ? 'other' : 'self', + 'timestamp': data['timestamp'].toString(), + 'spaceId': data['chatId'], + 'from': data['fromCAIP10'], + 'to': null, + }; + + if (includeRaw) { + baseEventData['raw'] = { + 'verificationProof': data['verificationProof'] ?? '', + }; + } + + return baseEventData; + } + + static dynamic mapToSpaceApproveEvent(data, bool includeRaw) { + final baseEventData = { + 'event': 'request', + 'origin': data['messageOrigin'] == 'other' ? 'self' : 'other', + 'timestamp': data['timestamp'], + 'spaceId': data['chatId'], + 'from': data['fromCAIP10'], + 'to': [data['toCAIP10']], + }; + + if (includeRaw) { + baseEventData['raw'] = { + 'verificationProof': data['verificationProof'] ?? '', + }; + } + + return baseEventData; + } + + static dynamic mapToStopSpaceEvent(data, bool includeRaw) { + final eventData = { + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['from'], + 'to': null, + 'event': data['eventType'], + }; + + if (includeRaw) { + eventData['raw'] = {'verificationProof': data['verificationProof']}; + } + return eventData; + } + + static dynamic mapToStartSpaceEvent(data, bool includeRaw) { + final eventData = { + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['from'], + 'to': null, + 'event': data['eventType'], + }; + + if (includeRaw) { + eventData['raw'] = {'verificationProof': data['verificationProof']}; + } + return eventData; + } + + static dynamic mapToLeaveSpaceEvent(data, bool includeRaw) { + final eventData = { + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['from'], + 'to': data['to'], + 'event': data['eventType'], + }; + + if (includeRaw) { + eventData['raw'] = {'verificationProof': data['verificationProof']}; + } + return eventData; + } + + static dynamic mapToJoinSpaceEvent(data, bool includeRaw) { + final eventData = { + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['from'], + 'to': data['to'], + 'event': data['eventType'], + }; + + if (includeRaw) { + eventData['raw'] = {'verificationProof': data['verificationProof']}; + } + return eventData; + } + + static dynamic mapToRemoveSpaceEvent(data, bool includeRaw) { + final eventData = { + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['from'], + 'to': data['to'], + 'event': 'remove', + }; + + if (includeRaw) { + eventData['raw'] = {'verificationProof': data['verificationProof']}; + } + return eventData; + } + + static dynamic mapToRequestSpaceEvent(data, bool includeRaw) { + final eventData = { + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['from'], + 'to': data['to'], + 'event': MessageEventType.request, + }; + + if (includeRaw) { + eventData['raw'] = {'verificationProof': data['verificationProof']}; + } + return eventData; + } + + static dynamic mapToUpdateSpaceEvent(data, bool includeRaw) { + final baseEventData = { + 'event': data['eventType'], + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['spaceCreator'], + 'meta': { + 'name': data['spaceName'], + 'description': data['spaceDescription'], + 'image': data['spaceImage'], + 'owner': data['spaceCreator'], + 'private': !(data['isPublic'] ?? false), + 'rules': data['rules'] ?? {}, + }, + }; + + if (includeRaw) { + baseEventData['raw'] = { + 'verificationProof': data['verificationProof'] ?? '', + }; + } + + return baseEventData; + } + + static dynamic mapToCreateSpaceEvent(data, bool includeRaw) { + final baseEventData = { + 'event': data['eventType'], + 'origin': data['messageOrigin'], + 'timestamp': data['timestamp'], + 'spaceId': data['spaceId'], + 'from': data['spaceCreator'], + 'meta': { + 'name': data['spaceName'], + 'description': data['spaceDescription'], + 'image': data['spaceImage'], + 'owner': data['spaceCreator'], + 'private': !(data['isPublic'] ?? false), + 'rules': data['rules'] ?? {}, + }, + }; + + if (includeRaw) { + baseEventData['raw'] = { + 'verificationProof': data['verificationProof'] ?? '', + }; + } + + return baseEventData; + } } diff --git a/lib/src/pushstream/src/models/models.dart b/lib/src/pushstream/src/models/models.dart index 05b8119..66e6995 100644 --- a/lib/src/pushstream/src/models/models.dart +++ b/lib/src/pushstream/src/models/models.dart @@ -29,8 +29,15 @@ class PushStreamInitializeOptions { class PushStreamFilter { final List? channels; final List? chats; + final List? space; + final List? video; - PushStreamFilter({this.channels, this.chats}); + PushStreamFilter({ + this.channels, + this.chats, + this.space, + this.video, + }); } class PushStreamConnection { @@ -50,6 +57,8 @@ enum STREAM { NOTIF_OPS, CHAT, CHAT_OPS, + SPACE, + SPACE_OPS, CONNECT, DISCONNECT, } @@ -67,6 +76,10 @@ extension STREAMExtension on STREAM { return 'STREAM.NOTIF_OPS'; case STREAM.CHAT: return 'STREAM.CHAT'; + case STREAM.SPACE: + return 'STREAM.SPACE'; + case STREAM.SPACE_OPS: + return 'STREAM.SPACE_OPS'; case STREAM.CHAT_OPS: return 'STREAM.CHAT_OPS'; case STREAM.CONNECT: @@ -87,6 +100,17 @@ class ProposedEventNames { static const CreateGroup = 'chat.group.create'; static const UpdateGroup = 'chat.group.update'; static const Remove = 'chat.group.participant.remove'; + + static const CreateSpace = 'space.create'; + static const UpdateSpace = 'space.update'; + static const SpaceRequest = 'space.request'; + static const SpaceAccept = 'space.accept'; + static const SpaceReject = 'space.reject'; + static const LeaveSpace = 'space.participant.leave'; + static const JoinSpace = 'space.participant.join'; + static const SpaceRemove = 'space.participant.remove'; + static const StartSpace = 'space.start'; + static const StopSpace = 'space.stop'; } class GroupEventType { @@ -97,6 +121,16 @@ class GroupEventType { static const remove = 'remove'; } +class SpaceEventType { + static const createSpace = 'createSpace'; + static const updateSpace = 'updateSpace'; + static const join = 'joinSpace'; + static const leave = 'leaveSpace'; + static const remove = 'remove'; + static const stop = 'stop'; + static const start = 'start'; +} + class MessageEventType { static const message = 'message'; static const request = 'request'; diff --git a/lib/src/pushstream/src/push_stream.dart b/lib/src/pushstream/src/push_stream.dart index 5d377eb..15e0357 100644 --- a/lib/src/pushstream/src/push_stream.dart +++ b/lib/src/pushstream/src/push_stream.dart @@ -245,6 +245,55 @@ class PushStream extends EventEmitter { log('Error handling CHAT_RECEIVED_MESSAGE event:$error \t Data:$data'); } }); + + pushChatSocket!.on(EVENTS.SPACES, (data) async { + try { + final modifiedData = + DataModifier.handleSpaceEvent(data: data, includeRaw: _raw); + modifiedData['event'] = + DataModifier.convertToProposedNameForSpace(modifiedData['event']); + + DataModifier.handleToField(modifiedData); + + if (_shouldEmitSpace(data.spaceId)) { + if (data.eventType == SpaceEventType.join || + data.eventType == SpaceEventType.leave || + data.eventType == MessageEventType.request || + data.eventType == SpaceEventType.remove || + data.eventType == SpaceEventType.start || + data.eventType == SpaceEventType.stop) { + if (shouldEmit(STREAM.SPACE)) { + emit(STREAM.SPACE.value, modifiedData); + } + } else { + if (shouldEmit(STREAM.SPACE_OPS)) { + emit(STREAM.SPACE_OPS.value, modifiedData); + } + } + } + } catch (e) { + log('Error handling SPACES event: $e, Data: $data'); + } + }); + + pushChatSocket!.on(EVENTS.SPACES_MESSAGES, (data) async { + try { + final modifiedData = + DataModifier.handleSpaceEvent(data: data, includeRaw: _raw); + modifiedData.event = + DataModifier.convertToProposedNameForSpace(modifiedData.event); + + DataModifier.handleToField(modifiedData); + + if (_shouldEmitSpace(data.spaceId)) { + if (shouldEmit(STREAM.SPACE)) { + emit(STREAM.SPACE.value, modifiedData); + } + } + } catch (e) { + log('Error handling SPACES event: $e, Data: $data'); + } + }); } if (pushNotificationSocket != null) { @@ -298,6 +347,10 @@ class PushStream extends EventEmitter { } } + bool connected() => + (pushNotificationSocket != null && pushNotificationSocket!.connected) || + (pushChatSocket != null && pushChatSocket!.connected); + Future disconnect() async { if (pushChatSocket != null) { pushChatSocket!.disconnect(); @@ -328,4 +381,14 @@ class PushStream extends EventEmitter { return _options.filter!.channels!.contains(dataChannelId); } + + bool _shouldEmitSpace(String dataSpaceId) { + if (_options.filter?.space != null || + _options.filter!.space!.isNotEmpty || + _options.filter!.space!.contains('*')) { + return true; + } + + return _options.filter!.space!.contains(dataSpaceId); + } }