+ {!next &&
+ (isCurrentUser ? (
+ isError ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ ))}
{prev ? null : (
, read:
, default:
},
+ accent: {
+ sent:
,
+ read:
,
+ decryption_failed:
,
+ default:
,
+ },
white: {
sent:
,
read:
,
+ decryption_failed:
,
default:
,
},
};
diff --git a/src/services/conversationsService.js b/src/services/conversationsService.js
index 11d79fc5..6ba6f67d 100644
--- a/src/services/conversationsService.js
+++ b/src/services/conversationsService.js
@@ -1,6 +1,7 @@
import DownloadManager from "@src/adapters/downloadManager";
import api from "@api/api";
import eventEmitter from "@event/eventEmitter";
+import isEqualsNativeIds from "@utils/user/isEqualsNativeIds";
import isHeic from "@utils/media/is_heic";
import navigateTo from "@utils/navigation/navigate_to";
import processFile from "@utils/media/process_file";
diff --git a/src/services/encryptionService.js b/src/services/encryptionService.js
index d0ead2c8..2f7566bd 100644
--- a/src/services/encryptionService.js
+++ b/src/services/encryptionService.js
@@ -1,5 +1,8 @@
import CryptoJS from "crypto-js";
import api from "@api/api";
+import getOpponentId from "@utils/user/get_opponent_id";
+import indexedDB from "@store/indexedDB";
+import messagesService from "./messagesService";
import initVodozemac, {
Account,
OlmMessage,
@@ -15,6 +18,7 @@ class EncryptionService {
#encryptionSessions = {};
#account = null;
#vodozemacInitialized = false;
+ visibleBody = "Can`t decrypt this message, not for this device";
constructor() {
this.#initializeVodozemac();
@@ -30,6 +34,10 @@ class EncryptionService {
}
}
+ markDecrypionFailedMessages(cid, mids) {
+ api.markDecrypionFailedMessages({ cid, mids });
+ }
+
hasAccount() {
return !!this.#account;
}
@@ -44,6 +52,11 @@ class EncryptionService {
await localforage.clear();
}
+ async clearStoredSessionWithUser(userId) {
+ delete this.#encryptionSessions[userId];
+ await localforage.removeItem(`encryptedSession${userId}`);
+ }
+
encryptMessage(text, userId) {
const session = this.#encryptionSessions[userId];
return session.encrypt(text);
@@ -61,14 +74,18 @@ class EncryptionService {
console.log(
"[encryption] Encrypted session with the opponent is missing"
);
- return "Can`t decrypt this message, not for this device";
+ return this.visibleBody;
}
try {
return session.decrypt(olmMessage);
} catch (error) {
console.log("[encryption] Failed to decrypt an encrypted message", error);
- return "Can`t decrypt this message, not for this device";
+ this.markDecrypionFailedMessages(olmMessageParams.cid, [
+ olmMessageParams._id,
+ ]);
+
+ return this.visibleBody;
}
}
@@ -274,6 +291,7 @@ class EncryptionService {
console.log("Encrypted session from local store:", session);
const decryptMessage = this.decryptMessage(olmMessageParams, userId);
+ indexedDB.upsertEncryptionMessage(olmMessageParams._id, decryptMessage);
store.dispatch(
upsertMessage({ _id: olmMessageParams._id, body: decryptMessage })
);
@@ -319,20 +337,26 @@ class EncryptionService {
if (olmMessage) {
console.log("Create session with olmMessage: ", olmMessage);
+ let decryptMessage = this.visibleBody;
try {
const inboundSession = this.#account.create_inbound_session(
userKeys.identity_key,
olmMessage
);
- const decryptMessage = `${inboundSession.plaintext}`;
+ decryptMessage = `${inboundSession.plaintext}`;
session = inboundSession.session;
store.dispatch(
upsertMessage({ _id: olmMessageParams._id, body: decryptMessage })
);
} catch (error) {
+ this.markDecrypionFailedMessages(olmMessageParams.cid, [
+ olmMessageParams._id,
+ ]);
+
console.error("Failed to create inbound session:", error);
}
+ indexedDB.upsertEncryptionMessage(olmMessageParams._id, decryptMessage);
//check if the top block worked successfully -> mb need to clear the session param
if (session) {
@@ -361,6 +385,22 @@ class EncryptionService {
}
}
+ async createNewSessionAndSendMessage(message) {
+ console.log("[encryption] Recreate a new encrypted session");
+ const messageParams = Object.assign({}, message);
+ const { _id: mid, cid, from } = messageParams;
+
+ const conversation = store.getState().conversations.entities[cid];
+ const opponentId = getOpponentId(conversation, from);
+
+ await this.clearStoredSessionWithUser(opponentId);
+ await this.createEncryptionSession(opponentId);
+
+ await messagesService.removeMessageFromLocalStore(mid, cid);
+
+ await messagesService.sendEncryptedMessage(messageParams, opponentId);
+ }
+
async encrypteDataForLocalStore(data) {
const secretKey = await this.#getPickleKey(
"pickleKey",
diff --git a/src/services/garbageCleaningService.js b/src/services/garbageCleaningService.js
index f1633d1d..6c6bbd6f 100644
--- a/src/services/garbageCleaningService.js
+++ b/src/services/garbageCleaningService.js
@@ -12,8 +12,8 @@ import { updateNetworkState } from "@store/values/NetworkState";
class GarbageCleaningService {
async clearConversationMessages(cid) {
if (!cid) return;
- store.dispatch(clearMessagesToLocalLimit(cid));
store.dispatch(clearMessageIdsToLocalLimit(cid));
+ store.dispatch(clearMessagesToLocalLimit(cid));
}
async resetDataOnAuth() {
diff --git a/src/services/messagesService.js b/src/services/messagesService.js
index e604b1af..da828d34 100644
--- a/src/services/messagesService.js
+++ b/src/services/messagesService.js
@@ -10,9 +10,9 @@ import { addUser } from "@store/values/Participants";
import {
addMessage,
addMessages,
- markMessagesAsRead,
removeMessage,
selectActiveConversationMessages,
+ updateMessagesStatus,
upsertMessage,
upsertMessages,
} from "@store/values/Messages";
@@ -20,6 +20,7 @@ import { setSelectedConversation } from "@store/values/SelectedConversation";
import {
markConversationAsRead,
removeChat,
+ removeMessageFromConversation,
updateLastMessageField,
upsertChat,
upsertParticipants,
@@ -38,6 +39,7 @@ class MessagesService {
message,
message.from
);
+ indexedDB.upsertEncryptionMessage(message._id, decryptedMessage);
store.dispatch(
upsertMessage({
_id: message._id,
@@ -49,8 +51,10 @@ class MessagesService {
constructor() {
api.onMessageStatusListener = (message) => {
- indexedDB.markMessagesAsRead(message.ids);
- store.dispatch(markMessagesAsRead(message.ids));
+ indexedDB.updateMessagesStatus(message.ids, "read");
+ store.dispatch(
+ updateMessagesStatus({ mids: message.ids, status: "read" })
+ );
store.dispatch(
markConversationAsRead({
cid: message.cid,
@@ -59,6 +63,13 @@ class MessagesService {
);
};
+ api.onMessageDecryptionFailedListener = (message) => {
+ indexedDB.updateMessagesStatus(message.ids, "decryption_failed");
+ store.dispatch(
+ updateMessagesStatus({ mids: message.ids, status: "decryption_failed" })
+ );
+ };
+
api.onMessageListener = async (message) => {
const attachments = message.attachments;
if (attachments) {
@@ -201,11 +212,15 @@ class MessagesService {
handleRetrievedMessages(messages) {
const messagesIds = messages.map((el) => el._id).reverse();
- const messagesRedux =
- selectActiveConversationMessages(store.getState()) || [];
+ const messagesReduxIds = (
+ selectActiveConversationMessages(store.getState()) || []
+ ).map((el) => el._id);
const uniqueMessageIds = [
- ...new Set([...messagesIds, ...messagesRedux.map((el) => el._id)]),
+ ...new Set([
+ ...messagesIds.filter((el) => !messagesReduxIds.includes(el)),
+ ...messagesReduxIds,
+ ]),
];
store.dispatch(addMessages(messages));
@@ -230,6 +245,15 @@ class MessagesService {
return this.handleRetrievedMessages(messagesDB);
}
+ const lastExistMessage = messagesDB[0];
+ if (lastExistMessage) {
+ params.updated_at = {
+ gt:
+ lastExistMessage.created_at ||
+ new Date(lastExistMessage.t * 1000).toISOString(),
+ };
+ }
+
const messagesAPI = await api.messageList(params);
await indexedDB.insertManyMessages(messagesAPI);
@@ -245,7 +269,7 @@ class MessagesService {
};
const allConversationMessages = Object.values(
- store.getState().messages.entities
+ selectActiveConversationMessages(store.getState()) || {}
);
const lastMessage = allConversationMessages.splice(-1)[0];
@@ -258,9 +282,9 @@ class MessagesService {
}
try {
- if (allConversationMessages.length === params.limit) return;
+ if (allConversationMessages.length >= params.limit) return;
- let messages = await this.retrieveMessages(params);
+ await this.retrieveMessages(params);
const conv =
store.getState().conversations?.entities?.[this.currentChatId];
@@ -276,15 +300,6 @@ class MessagesService {
})
);
}
-
- if (conv.is_encrypted) {
- setTimeout(() => {
- //replace in the future, should be called after the session is created
- messages.forEach(
- async (message) => await this.#tryToCreateESession(message)
- );
- }, 500);
- }
} catch (error) {
console.log(error);
store.dispatch(removeChat(cid));
@@ -329,6 +344,12 @@ class MessagesService {
await this.sendMessage(message);
}
+
+ async removeMessageFromLocalStore(mid, cid) {
+ store.dispatch(removeMessageFromConversation({ mid, cid }));
+ store.dispatch(removeMessage(mid));
+ await indexedDB.removeMessage(mid);
+ }
}
const messagesService = new MessagesService();
diff --git a/src/store/indexedDB.js b/src/store/indexedDB.js
index 77c7270e..3d147b48 100644
--- a/src/store/indexedDB.js
+++ b/src/store/indexedDB.js
@@ -10,9 +10,9 @@ class IndexedDB {
this.db = db;
}
- markMessagesAsRead(mids) {
+ updateMessagesStatus(mids, status) {
this.db.messages.bulkUpdate(
- mids.map((id) => ({ key: id, changes: { status: "read" } }))
+ mids.map((id) => ({ key: id, changes: { status } }))
);
}
@@ -62,9 +62,19 @@ class IndexedDB {
);
}
+ async upsertEncryptionMessage(mid, body) {
+ await this.db.messages.update(mid, {
+ body: await encryptionService.encrypteDataForLocalStore(body),
+ });
+ }
+
async removeAllMessages() {
await this.db.messages.clear();
}
+
+ async removeMessage(mid) {
+ await this.db.messages.delete(mid);
+ }
}
const indexedDB = new IndexedDB();
diff --git a/src/store/values/Conversations.js b/src/store/values/Conversations.js
index e194dcbb..690b730a 100644
--- a/src/store/values/Conversations.js
+++ b/src/store/values/Conversations.js
@@ -181,7 +181,15 @@ export const conversations = createSlice({
clearMessageIdsToLocalLimit: (state, { payload }) => {
conversationsAdapter.upsertOne(state, {
_id: payload,
- messagesIds: state.entities[payload].messagesIds.slice(-30),
+ messagesIds: state.entities[payload].messagesIds?.slice(-30),
+ });
+ },
+ removeMessageFromConversation: (state, { payload }) => {
+ conversationsAdapter.upsertOne(state, {
+ _id: payload.cid,
+ messagesIds: state.entities[payload.cid].messagesIds.filter(
+ (mid) => mid !== payload.mid
+ ),
});
},
},
@@ -200,6 +208,7 @@ export const {
upsertChat,
upsertParticipants,
clearMessageIdsToLocalLimit,
+ removeMessageFromConversation,
} = conversations.actions;
export default conversations.reducer;
diff --git a/src/store/values/Messages.js b/src/store/values/Messages.js
index 428c65ea..b7368f33 100644
--- a/src/store/values/Messages.js
+++ b/src/store/values/Messages.js
@@ -26,20 +26,18 @@ export const messages = createSlice({
addMessages: messagesAdapter.addMany,
upsertMessage: messagesAdapter.upsertOne,
upsertMessages: messagesAdapter.upsertMany,
- markMessagesAsRead: (state, action) => {
- const mids = action.payload
+ updateMessagesStatus: (state, { payload: { mids, status } }) => {
+ const upsertParams = mids
.filter((id) => !!state.entities[id])
- .map((id) => {
- return { _id: id, status: "read" };
- });
- messagesAdapter.upsertMany(state, mids);
+ .map((id) => ({ _id: id, status }));
+ messagesAdapter.upsertMany(state, upsertParams);
},
clearMessagesToLocalLimit: (state, { payload }) => {
const messageIds = Object.values(state.entities)
.filter((message) => message.cid === payload)
- .sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
- .slice(0, 30)
+ .splice(30)
.map((message) => message._id);
+
messagesAdapter.removeMany(state, messageIds);
},
removeMessage: messagesAdapter.removeOne,
@@ -65,7 +63,9 @@ export const {
addMessages,
upsertMessage,
upsertMessages,
+ updateMessagesStatus,
markMessagesAsRead,
+ markDecryptionFailedMessages,
removeMessage,
clearMessagesToLocalLimit,
} = messages.actions;
diff --git a/src/styles/GlobalParam.css b/src/styles/GlobalParam.css
index 6e79a63c..b12479fe 100644
--- a/src/styles/GlobalParam.css
+++ b/src/styles/GlobalParam.css
@@ -32,6 +32,7 @@
--color-black-50: rgba(0, 0, 0, 0.5);
--color-black-75: rgba(0, 0, 0, 0.75);
--color-grey-50: rgba(26, 26, 26, 0.5);
+ --color-red-light: #f19ba0;
--color-red: #df2e38;
--color-bg-light: #f6f6f6;
--color-bg-light-25: rgba(246, 246, 246, 0.25);
diff --git a/src/styles/hub/elements/ChatMessage.css b/src/styles/hub/elements/ChatMessage.css
index 4d6f18dc..c35e065b 100644
--- a/src/styles/hub/elements/ChatMessage.css
+++ b/src/styles/hub/elements/ChatMessage.css
@@ -156,6 +156,12 @@
background-color: var(--color-accent-dark);
}
+.message__container--my.danger .message-content__container {
+ background-color: var(--color-red-light);
+
+ cursor: pointer;
+}
+
.message__container--my .photo__container,
.message__container--my .content__text,
.message__container--my .content__uname,
diff --git a/src/utils/user/isEqualsNativeIds.js b/src/utils/user/isEqualsNativeIds.js
new file mode 100644
index 00000000..e01eb79c
--- /dev/null
+++ b/src/utils/user/isEqualsNativeIds.js
@@ -0,0 +1,3 @@
+export default function isEqualsNativeIds(id1, id2) {
+ return id1.toString() === id2.toString();
+}