Skip to content

Commit

Permalink
feat(chats): done encrypted chats
Browse files Browse the repository at this point in the history
  • Loading branch information
lambiengcode committed Sep 17, 2024
2 parents 58e8fc6 + e4faf2d commit d082df7
Show file tree
Hide file tree
Showing 37 changed files with 1,300 additions and 172 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.5.0

* [Feat]: encrypted chats

## 1.4.16

* [Chore]: upgrade web: ^1.0.0
Expand Down
5 changes: 5 additions & 0 deletions lib/constants/api_enpoints.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ class ApiEndpoints {
// Users
static const String users = 'users';
static const String username = 'users/username';
static const String searchUsers = 'users/search';

// Meetings
static const String meetings = 'meetings';
static const String joinWithPassword = 'meetings/join/password';
static const String joinWithoutPassword = 'meetings/join';
static const String meetingConversations = 'meetings/conversations';
static const String meetingMembers = 'meetings/members';
static const String acceptInvite = 'meetings/members/accept';

// Chats
static const String chats = 'chats';
static const String chatsConversations = 'chats/conversations';
}
4 changes: 4 additions & 0 deletions lib/constants/socket_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ class SocketEvent {
static const String sendPodNameSSC = 'SEND_POD_NAME_SSC';
static const String reconnect = 'reconnect_CSS';
static const String destroy = 'destroy';

static const String sendMessageSSC = 'SEND_MESSAGE_SSC';
static const String updateMessageSSC = 'UPDATE_MESSAGE_SSC';
static const String deleteMessageSSC = 'DELETE_MESSAGE_SSC';
}
6 changes: 1 addition & 5 deletions lib/core/api/auth/datasources/auth_remote_datasource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ class AuthRemoteDataSourceImpl extends AuthRemoteDataSource {
ApiEndpoints.auth,
);

if (response.statusCode == StatusCode.noContent) {
return true;
}

return false;
return response.statusCode == StatusCode.noContent;
}
}
5 changes: 2 additions & 3 deletions lib/core/api/base/dio_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:waterbus_sdk/constants/http_status_code.dart';
import 'package:waterbus_sdk/core/api/auth/datasources/auth_local_datasource.dart';
import 'package:waterbus_sdk/core/api/base/base_remote_data.dart';
import 'package:waterbus_sdk/utils/extensions/duration_extensions.dart';
import 'package:waterbus_sdk/utils/http/dio_transformer.dart';
import 'package:waterbus_sdk/utils/queues/completer_queue.dart';

typedef TokensCallback = Function(
Expand Down Expand Up @@ -46,8 +45,8 @@ class DioConfiguration {

dioClient.httpClientAdapter = ConversionLayerAdapter(rhttpAdapter);

// Transform json with compute
dioClient.transformer = FlutterTransformer();
// // Transform json with compute
// dioClient.transformer = FlutterTransformer();
}

// Integration retry
Expand Down
153 changes: 153 additions & 0 deletions lib/core/api/chat/datasources/chat_remote_datasource.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import 'dart:isolate';

import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';

import 'package:waterbus_sdk/constants/api_enpoints.dart';
import 'package:waterbus_sdk/constants/http_status_code.dart';
import 'package:waterbus_sdk/core/api/base/base_remote_data.dart';
import 'package:waterbus_sdk/flutter_waterbus_sdk.dart';
import 'package:waterbus_sdk/utils/encrypt/encrypt.dart';

abstract class ChatRemoteDataSource {
Future<List<Meeting>> getConversations({
required int skip,
required int limit,
required int status,
});
Future<bool> deleteConversation({required int meetingId});
Future<Meeting?> leaveConversation({required int code});
Future<Meeting?> addMember({required int code, required int userId});
Future<Meeting?> deleteMember({required int code, required int userId});
Future<Meeting?> acceptInvite({required int code});
}

@LazySingleton(as: ChatRemoteDataSource)
class ChatRemoteDataSourceImpl extends ChatRemoteDataSource {
final BaseRemoteData _remoteData;
ChatRemoteDataSourceImpl(
this._remoteData,
);

@override
Future<List<Meeting>> getConversations({
required int skip,
required int limit,
required int status,
}) async {
final Response response = await _remoteData.getRoute(
"${ApiEndpoints.meetingConversations}/$status",
query: "limit=$limit&skip=$skip",
);

if ([StatusCode.ok, StatusCode.created].contains(response.statusCode)) {
final ReceivePort receivePort = ReceivePort();

final Map<String, dynamic> message = {
"conversations": (response.data as List)
.map((meeting) => Meeting.fromMap(meeting))
.toList(),
"sendPort": receivePort.sendPort,
"key": WaterbusSdk.privateMessageKey,
};

await Isolate.spawn(_handleDecryptLastMessage, message);

return await receivePort.first;
}

return [];
}

static Future<void> _handleDecryptLastMessage(
Map<String, dynamic> map,
) async {
final List<Meeting> conversations = map['conversations'];
final SendPort sendPort = map['sendPort'];
final String key = map['key'];
final List<Meeting> conversationsDecrypt = [];
for (final Meeting conversation in conversations) {
if (conversation.latestMessage == null) continue;

final String decrypt = await EncryptAES().decryptAES256(
cipherText: conversation.latestMessage?.data ?? "",
key: key,
);

conversationsDecrypt.add(
conversation.copyWith(
latestMessage: conversation.latestMessage?.copyWith(data: decrypt),
),
);
}

Isolate.exit(sendPort, conversationsDecrypt);
}

@override
Future<bool> deleteConversation({required int meetingId}) async {
final response = await _remoteData.deleteRoute(
"${ApiEndpoints.chatsConversations}/$meetingId",
);

return [StatusCode.ok, StatusCode.created].contains(response.statusCode);
}

@override
Future<Meeting?> leaveConversation({required int code}) async {
final Response response = await _remoteData.deleteRoute(
'${ApiEndpoints.meetings}/$code',
);

if (response.statusCode == StatusCode.ok) {
final Map<String, dynamic> rawData = response.data;
return Meeting.fromMap(rawData);
}

return null;
}

@override
Future<Meeting?> acceptInvite({required int code}) async {
final Response response = await _remoteData.postRoute(
'${ApiEndpoints.acceptInvite}/$code',
);

if ([StatusCode.ok, StatusCode.created].contains(response.statusCode)) {
return Meeting.fromMap(response.data);
}

return null;
}

@override
Future<Meeting?> addMember({required int code, required int userId}) async {
final Response response = await _remoteData.postRoute(
'${ApiEndpoints.meetingMembers}/$code',
body: {"userId": userId},
);

if ([StatusCode.ok, StatusCode.created].contains(response.statusCode)) {
return Meeting.fromMap(response.data);
}

return null;
}

@override
Future<Meeting?> deleteMember({
required int code,
required int userId,
}) async {
final Response response = await _remoteData.deleteRoute(
'${ApiEndpoints.meetingMembers}/$code',
body: {"userId": userId},
);

if ([StatusCode.ok, StatusCode.created].contains(response.statusCode)) {
return Meeting.fromMap(response.data);
}

return null;
}
}
92 changes: 92 additions & 0 deletions lib/core/api/chat/repositories/chat_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:injectable/injectable.dart';

import 'package:waterbus_sdk/core/api/chat/datasources/chat_remote_datasource.dart';
import 'package:waterbus_sdk/flutter_waterbus_sdk.dart';

abstract class ChatRepository {
Future<List<Meeting>> getConversations({
required int status,
required int limit,
required int skip,
});
Future<bool> deleteConversation(int meetingId);
Future<Meeting?> leaveConversation({required int code});
Future<Meeting?> addMember({required int code, required int userId});
Future<Meeting?> deleteMember({required int code, required int userId});
Future<Meeting?> acceptInvite({required int code});
}

@LazySingleton(as: ChatRepository)
class ChatRepositoryImpl extends ChatRepository {
final ChatRemoteDataSource _remoteDataSource;

ChatRepositoryImpl(
this._remoteDataSource,
);

@override
Future<List<Meeting>> getConversations({
required int status,
required limit,
required skip,
}) async {
final List<Meeting> conversations =
await _remoteDataSource.getConversations(
skip: skip,
limit: limit,
status: status,
);

return conversations;
}

@override
Future<bool> deleteConversation(int meetingId) async {
final bool isSucceed = await _remoteDataSource.deleteConversation(
meetingId: meetingId,
);

return isSucceed;
}

@override
Future<Meeting?> leaveConversation({required int code}) async {
final Meeting? meeting = await _remoteDataSource.leaveConversation(
code: code,
);

return meeting;
}

@override
Future<Meeting?> acceptInvite({required int code}) async {
final Meeting? meeting = await _remoteDataSource.acceptInvite(
code: code,
);

return meeting;
}

@override
Future<Meeting?> addMember({required int code, required int userId}) async {
final Meeting? member = await _remoteDataSource.addMember(
code: code,
userId: userId,
);

return member;
}

@override
Future<Meeting?> deleteMember({
required int code,
required int userId,
}) async {
final Meeting? meeting = await _remoteDataSource.deleteMember(
code: code,
userId: userId,
);

return meeting;
}
}
30 changes: 1 addition & 29 deletions lib/core/api/meetings/datasources/meeting_remote_datesource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ abstract class MeetingRemoteDataSource {
required Meeting meeting,
});
Future<Meeting?> getInfoMeeting(int code);
Future<Meeting?> leaveMeeting({
required int code,
required int participantId,
});
}

@LazySingleton(as: MeetingRemoteDataSource)
Expand Down Expand Up @@ -107,26 +103,6 @@ class MeetingRemoteDataSourceImpl extends MeetingRemoteDataSource {
return null;
}

@override
Future<Meeting?> leaveMeeting({
required int code,
required int participantId,
}) async {
final Response response = await _remoteData.deleteRoute(
'${ApiEndpoints.meetings}/$code',
body: {
'participantId': participantId,
},
);

if (response.statusCode == StatusCode.ok) {
final Map<String, dynamic> rawData = response.data;
return Meeting.fromMap(rawData);
}

return null;
}

@override
Future<bool> updateMeeting({
required Meeting meeting,
Expand All @@ -137,10 +113,6 @@ class MeetingRemoteDataSourceImpl extends MeetingRemoteDataSource {
meeting.toMapCreate(password),
);

if (response.statusCode == StatusCode.ok) {
return true;
}

return false;
return response.statusCode == StatusCode.ok;
}
}
2 changes: 1 addition & 1 deletion lib/core/api/meetings/repositories/meeting_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class MeetingRepositoryImpl extends MeetingRepository {
final int indexOfMyParticipant = participants.lastIndexWhere(
(participant) => participantId != null
? participant.id == participantId
: participant.user.id == userId,
: participant.user?.id == userId,
);

if (indexOfMyParticipant == -1) return meeting;
Expand Down
Loading

0 comments on commit d082df7

Please sign in to comment.