Compare commits

...

4 Commits

Author SHA1 Message Date
Reinaldo Neto 36bc222707 minor tweak 2023-02-15 18:31:00 -03:00
Reinaldo Neto 21acdd9f2c refactor and tweaks 2023-02-15 18:21:38 -03:00
Reinaldo Neto 4ea8aabe6b parser inside the model 2023-02-02 12:34:04 -03:00
Reinaldo Neto eb9f973e47 [NEW] Quotes on E2EE Messages 2023-02-01 18:24:34 -03:00
13 changed files with 248 additions and 6 deletions

View File

@ -147,7 +147,10 @@ export interface IMessage extends IMessageFromServer {
editedAt?: string | Date;
}
export type TMessageModel = IMessage & Model;
export type TMessageModel = IMessage &
Model & {
asPlain: () => IMessage;
};
export type TAnyMessageModel = TMessageModel | TThreadModel | TThreadMessageModel;
export type TTypeMessages = IMessageFromServer | ILoadMoreMessage | IMessage;

View File

@ -38,4 +38,7 @@ export interface IThread extends IMessage {
draftMessage?: string;
}
export type TThreadModel = IThread & Model;
export type TThreadModel = IThread &
Model & {
asPlain: () => IMessage;
};

View File

@ -6,4 +6,7 @@ export interface IThreadMessage extends IMessage {
tmsg?: string;
}
export type TThreadMessageModel = IThreadMessage & Model;
export type TThreadMessageModel = IThreadMessage &
Model & {
asPlain: () => IMessage;
};

View File

@ -85,4 +85,47 @@ export default class Message extends Model {
@json('md', sanitizer) md;
@field('comment') comment;
asPlain() {
return {
id: this.id,
rid: this.subscription.id,
msg: this.msg,
t: this.t,
ts: this.ts,
u: this.u,
alias: this.alias,
parseUrls: this.parseUrls,
groupable: this.groupable,
avatar: this.avatar,
emoji: this.emoji,
attachments: this.attachments,
urls: this.urls,
_updatedAt: this._updatedAt,
status: this.status,
pinned: this.pinned,
starred: this.starred,
editedBy: this.editedBy,
reactions: this.reactions,
role: this.role,
drid: this.drid,
dcount: this.dcount,
dlm: this.dlm,
tmid: this.tmid,
tcount: this.tcount,
tlm: this.tlm,
replies: this.replies,
mentions: this.mentions,
channels: this.channels,
unread: this.unread,
autoTranslate: this.autoTranslate,
translations: this.translations,
tmsg: this.tmsg,
blocks: this.blocks,
e2e: this.e2e,
tshow: this.tshow,
md: this.md,
comment: this.comment
};
}
}

View File

@ -77,4 +77,42 @@ export default class Thread extends Model {
@field('e2e') e2e;
@field('draft_message') draftMessage;
asPlain() {
return {
id: this.id,
msg: this.msg,
t: this.t,
ts: this.ts,
u: this.u,
alias: this.alias,
parseUrls: this.parseUrls,
groupable: this.groupable,
avatar: this.avatar,
emoji: this.emoji,
attachments: this.attachments,
urls: this.urls,
_updatedAt: this._updatedAt,
status: this.status,
pinned: this.pinned,
starred: this.starred,
editedBy: this.editedBy,
reactions: this.reactions,
role: this.role,
drid: this.drid,
dcount: this.dcount,
dlm: this.dlm,
tmid: this.tmid,
tcount: this.tcount,
tlm: this.tlm,
replies: this.replies,
mentions: this.mentions,
channels: this.channels,
unread: this.unread,
autoTranslate: this.autoTranslate,
translations: this.translations,
e2e: this.e2e,
draftMessage: this.draftMessage
};
}
}

View File

@ -77,4 +77,42 @@ export default class ThreadMessage extends Model {
@field('draft_message') draftMessage;
@field('e2e') e2e;
asPlain() {
return {
id: this.id,
msg: this.msg,
t: this.t,
ts: this.ts,
u: this.u,
rid: this.rid,
alias: this.alias,
parseUrls: this.parseUrls,
groupable: this.groupable,
avatar: this.avatar,
emoji: this.emoji,
attachments: this.attachments,
urls: this.urls,
_updatedAt: this._updatedAt,
status: this.status,
pinned: this.pinned,
starred: this.starred,
editedBy: this.editedBy,
reactions: this.reactions,
role: this.role,
drid: this.drid,
dcount: this.dcount,
dlm: this.dlm,
tcount: this.tcount,
tlm: this.tlm,
replies: this.replies,
mentions: this.mentions,
channels: this.channels,
unread: this.unread,
autoTranslate: this.autoTranslate,
translations: this.translations,
draftMessage: this.draftMessage,
e2e: this.e2e
};
}
}

View File

@ -13,7 +13,7 @@ export const getMessageById = async (messageId: string | null) => {
try {
const result = await messageCollection.find(messageId);
return result;
} catch (error) {
} catch {
return null;
}
};

View File

@ -0,0 +1,25 @@
import { store } from '../../store/auxStore';
import { IAttachment, IMessage } from '../../../definitions';
import { getAvatarURL } from '../../methods/helpers';
export function createQuoteAttachment(message: IMessage, messageLink: string): IAttachment {
const { server, version: serverVersion } = store.getState().server;
const externalProviderUrl = (store.getState().settings?.Accounts_AvatarExternalProviderUrl as string) || '';
return {
text: message.msg,
...('translations' in message && { translations: message?.translations }),
message_link: messageLink,
author_name: message.alias || message.u.username,
author_icon: getAvatarURL({
avatar: message.u?.username && `/avatar/${message.u?.username}`,
type: message.t,
userId: message.u?._id,
server,
serverVersion,
externalProviderUrl
}),
attachments: message.attachments || [],
ts: message.ts
};
}

View File

@ -0,0 +1,12 @@
import { getMessageUrlRegex } from './getMessageUrlRegex';
describe('Should regex', () => {
test('a common quote separated by space', () => {
const quote = '[ ](https://open.rocket.chat/group/room?msg=rid) test';
expect(quote.match(getMessageUrlRegex())).toStrictEqual(['https://open.rocket.chat/group/room?msg=rid']);
});
test('a quote separated by break line', () => {
const quote = '[ ](https://open.rocket.chat/group/room?msg=rid)\ntest';
expect(quote.match(getMessageUrlRegex())).toStrictEqual(['https://open.rocket.chat/group/room?msg=rid']);
});
});

View File

@ -0,0 +1,3 @@
// https://github.com/RocketChat/Rocket.Chat/blob/0226236b871d12c62338111c70b65d5d406447a3/apps/meteor/lib/getMessageUrlRegex.ts#L1-L2
export const getMessageUrlRegex = (): RegExp =>
/([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g;

View File

@ -0,0 +1,14 @@
import { IMessage } from '../../../definitions';
export const mapMessageFromAPI = ({ attachments, tlm, ts, _updatedAt, ...message }: IMessage) => ({
...message,
ts: new Date(ts),
...(tlm && { tlm: new Date(tlm) }),
_updatedAt: new Date(_updatedAt),
...(attachments && {
attachments: attachments.map(({ ts, ...attachment }) => ({
...(ts && { ts: new Date(ts) }),
...(attachment as any)
}))
})
});

View File

@ -0,0 +1,17 @@
import { TMessageModel } from '../../../definitions';
export const mapMessageFromDB = (messageModel: TMessageModel) => {
const parsedMessage = messageModel.asPlain();
return {
...parsedMessage,
ts: new Date(parsedMessage.ts),
...(parsedMessage.tlm && { tlm: new Date(parsedMessage.tlm) }),
_updatedAt: new Date(parsedMessage._updatedAt),
...(parsedMessage.attachments && {
attachments: parsedMessage.attachments.map(({ ts, ...attachment }) => ({
...(ts && { ts: new Date(ts) }),
...(attachment as any)
}))
})
};
};

View File

@ -2,7 +2,9 @@ import EJSON from 'ejson';
import { Base64 } from 'js-base64';
import SimpleCrypto from 'react-native-simple-crypto';
import ByteBuffer from 'bytebuffer';
import parse from 'url-parse';
import getSingleMessage from '../methods/getSingleMessage';
import { IMessage, IUser } from '../../definitions';
import Deferred from './helpers/deferred';
import { debounce } from '../methods/helpers';
@ -21,6 +23,11 @@ import {
import { Encryption } from './index';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../constants';
import { Services } from '../services';
import { getMessageUrlRegex } from './helpers/getMessageUrlRegex';
import { mapMessageFromAPI } from './helpers/mapMessageFromApi';
import { mapMessageFromDB } from './helpers/mapMessageFromDB';
import { createQuoteAttachment } from './helpers/createQuoteAttachment';
import { getMessageById } from '../database/services/Message';
export default class EncryptionRoom {
ready: boolean;
@ -268,12 +275,15 @@ export default class EncryptionRoom {
tmsg = await this.decryptText(tmsg);
}
return {
const decryptedMessage: IMessage = {
...message,
tmsg,
msg,
e2e: E2E_STATUS.DONE
e2e: 'done'
};
const decryptedMessageWithQuote = await this.decryptQuoteAttachment(decryptedMessage);
return decryptedMessageWithQuote;
}
} catch {
// Do nothing
@ -281,4 +291,37 @@ export default class EncryptionRoom {
return message;
};
async decryptQuoteAttachment(message: IMessage) {
const urls = message?.msg?.match(getMessageUrlRegex()) || [];
await Promise.all(
urls.map(async (url: string) => {
const parsedUrl = parse(url, true);
const messageId = parsedUrl.query?.msg;
if (!messageId) {
return;
}
// From local db
const messageFromDB = await getMessageById(messageId);
if (messageFromDB && messageFromDB.e2e === 'done') {
const decryptedQuoteMessage = mapMessageFromDB(messageFromDB);
message.attachments = message.attachments || [];
const quoteAttachment = createQuoteAttachment(decryptedQuoteMessage, url);
return message.attachments.push(quoteAttachment);
}
// From API
const quotedMessageObject = await getSingleMessage(messageId);
if (!quotedMessageObject) {
return;
}
const decryptedQuoteMessage = await this.decrypt(mapMessageFromAPI(quotedMessageObject));
message.attachments = message.attachments || [];
const quoteAttachment = createQuoteAttachment(decryptedQuoteMessage, url);
return message.attachments.push(quoteAttachment);
})
);
return message;
}
}