From d5e3f7303543e384a5b0cd27cba1f81774ce160d Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 17 May 2022 17:17:25 +1000 Subject: [PATCH 1/8] fix: read receipts handling for private chats --- ts/models/conversation.ts | 39 +++++++++++++++++++---------------- ts/receiver/contentMessage.ts | 4 +--- ts/receiver/dataMessage.ts | 2 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 8ae1feebf..b5860ba01 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -231,7 +231,10 @@ export class ConversationModel extends Backbone.Model { if (newestUnreadDate > lastReadTimestamp) { this.lastReadTimestamp = newestUnreadDate; } - void markReadDebounced(newestUnreadDate); + + if (newestUnreadDate !== lastReadTimestamp) { + void markReadDebounced(newestUnreadDate); + } }; // Listening for out-of-band data updates @@ -1059,6 +1062,7 @@ export class ConversationModel extends Backbone.Model { } } + // tslint:disable-next-line: cyclomatic-complexity public async markReadBouncy(newestUnreadDate: number, providedOptions: any = {}) { const lastReadTimestamp = this.lastReadTimestamp; if (newestUnreadDate < lastReadTimestamp) { @@ -1107,7 +1111,7 @@ export class ConversationModel extends Backbone.Model { const realUnreadCount = await this.getUnreadCount(); if (read.length === 0) { const cachedUnreadCountOnConvo = this.get('unreadCount'); - if (cachedUnreadCountOnConvo !== read.length) { + if (cachedUnreadCountOnConvo !== realUnreadCount) { // reset the unreadCount on the convo to the real one coming from markRead messages on the db this.set({ unreadCount: realUnreadCount }); await this.commit(); @@ -1142,25 +1146,24 @@ export class ConversationModel extends Backbone.Model { // conversation is viewed, another error message shows up for the contact read = read.filter(item => !item.hasErrors); - if (this.isPublic()) { + if (!this.isPrivate() || !read.length || !options.sendReadReceipts) { return; } - if (this.isPrivate() && read.length && options.sendReadReceipts) { - window?.log?.info( - `Sending ${read.length} read receipts?`, - Storage.get(SettingsKey.settingsReadReceipt) || false - ); - const dontSendReceipt = this.isBlocked() || this.isIncomingRequest(); - if (Storage.get(SettingsKey.settingsReadReceipt) && !dontSendReceipt) { - const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array; - const receiptMessage = new ReadReceiptMessage({ - timestamp: Date.now(), - timestamps, - }); + const settingsReadReceiptEnabled = Storage.get(SettingsKey.settingsReadReceipt) || false; + const sendReceipt = + settingsReadReceiptEnabled && !this.isBlocked() && !this.isIncomingRequest(); - const device = new PubKey(this.id); - await getMessageQueue().sendToPubKey(device, receiptMessage); - } + if (sendReceipt) { + window?.log?.info(`Sending ${read.length} read receipts.`); + + const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array; + const receiptMessage = new ReadReceiptMessage({ + timestamp: Date.now(), + timestamps, + }); + + const device = new PubKey(this.id); + await getMessageQueue().sendToPubKey(device, receiptMessage); } } diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 8ebc37455..45bdbd5e4 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -447,11 +447,9 @@ export async function innerHandleSwarmContentMessage( } function onReadReceipt(readAt: number, timestamp: number, source: string) { - const { storage } = window; - window?.log?.info('read receipt', source, timestamp); - if (!storage.get(SettingsKey.settingsReadReceipt)) { + if (!Storage.get(SettingsKey.settingsReadReceipt)) { return; } diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index cea551632..ff459a935 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -201,7 +201,7 @@ export async function handleSwarmDataMessage( ); window?.log?.info( - `Handle dataMessage about convo ${convoIdToAddTheMessageTo} from user: ${convoIdOfSender}: ${cleanDataMessage}` + `Handle dataMessage about convo ${convoIdToAddTheMessageTo} from user: ${convoIdOfSender}` ); // remove the prefix from the source object so this is correct for all other From 3bfc2be68140d81fdb85f8769aac6092d40632b1 Mon Sep 17 00:00:00 2001 From: dement6d <93228501+dement6d@users.noreply.github.com> Date: Tue, 17 May 2022 12:11:26 +0200 Subject: [PATCH 2/8] Update BUILDING.md add note about required package for build-release script --- BUILDING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/BUILDING.md b/BUILDING.md index a3f13486f..6a8359d57 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -124,6 +124,15 @@ yarn start-prod # start the app on production mode (currently this is the only o ### Commands +The `rpm` package is required for running the build-release script. Run the appropriate command to install the `rpm` package: +```sh +sudo pacman -S rpm # Arch +``` +```sh +sudo apt install rpm # Ubuntu/Debian +``` + + Run the following to build the binaries for your specific system OS. ``` From bfda4c7c01ea03fbad28c0d2c10b5e528c28e1bb Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 18 May 2022 14:53:55 +1000 Subject: [PATCH 3/8] fix: make sure a convo exists before returning it in search results There is an edge case if you are in the search results page, and delete (or get a convo deleted from the network). The corresponding convo does not exist anymore but the search result selector still tries to extract values from it. This commit fixes that issue by returning early if the corresponding convo is not there anymore --- ts/state/selectors/search.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts index cc6d81aad..b8a963d27 100644 --- a/ts/state/selectors/search.ts +++ b/ts/state/selectors/search.ts @@ -25,6 +25,11 @@ export const getSearchResults = createSelector( searchState.contactsAndGroups.map(id => { const value = lookup[id]; + // on some edges cases, we have an id but no corresponding convo because it matches a query but the conversation was removed. + if (!value) { + return null; + } + // Don't return anything when activeAt is unset (i.e. no current conversations with this user) if (value.activeAt === undefined || value.activeAt === 0) { //activeAt can be 0 when linking device From 83b44d0abff675a13399362306bd6bc04678cdcb Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 19 May 2022 13:03:24 +1000 Subject: [PATCH 4/8] fix: disable auto play of audio messages on message first load --- ts/components/conversation/H5AudioPlayer.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index ce3658ad0..ed4bfb6fb 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -18,9 +18,10 @@ export const AudioPlayerWithEncryptedFile = (props: { contentType: string; messageId: string; }) => { + const { messageId, contentType, src } = props; const dispatch = useDispatch(); const [playbackSpeed, setPlaybackSpeed] = useState(1.0); - const { urlToLoad } = useEncryptedFileFetch(props.src, props.contentType, false); + const { urlToLoad } = useEncryptedFileFetch(src, contentType, false); const player = useRef(null); const autoPlaySetting = useSelector(getAudioAutoplay); @@ -30,16 +31,19 @@ export const AudioPlayerWithEncryptedFile = (props: { useEffect(() => { // updates playback speed to value selected in context menu - if (player.current?.audio.current?.playbackRate) { + if ( + player.current?.audio.current && + player.current?.audio.current?.playbackRate !== playbackSpeed + ) { player.current.audio.current.playbackRate = playbackSpeed; } }, [playbackSpeed, player]); useEffect(() => { - if (props.messageId === nextMessageToPlayId) { + if (messageId !== undefined && messageId === nextMessageToPlayId) { player.current?.audio.current?.play(); } - }, [props.messageId, nextMessageToPlayId, player]); + }, [messageId, nextMessageToPlayId, player]); const triggerPlayNextMessageIfNeeded = (endedMessageId: string) => { const justEndedMessageIndex = messageProps.findIndex( @@ -75,8 +79,8 @@ export const AudioPlayerWithEncryptedFile = (props: { const onEnded = () => { // if audio autoplay is enabled, call method to start playing // the next playable message - if (autoPlaySetting === true && props.messageId) { - triggerPlayNextMessageIfNeeded(props.messageId); + if (autoPlaySetting === true && messageId) { + triggerPlayNextMessageIfNeeded(messageId); } }; @@ -87,6 +91,8 @@ export const AudioPlayerWithEncryptedFile = (props: { style={{ pointerEvents: multiSelectMode ? 'none' : 'inherit' }} layout="horizontal-reverse" showSkipControls={false} + autoPlay={false} + autoPlayAfterSrcChange={false} showJumpControls={false} showDownloadProgress={false} listenInterval={100} From c3b9cd2b41b94653cc25de893f0e062653eb89e4 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 19 May 2022 13:10:10 +1000 Subject: [PATCH 5/8] fix: error when trying to reply to an attachment Relates #2327 --- ts/data/data.ts | 6 ++++++ ts/models/message.ts | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ts/data/data.ts b/ts/data/data.ts index 2191d1279..5cfd0c232 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -90,6 +90,12 @@ function _cleanData(data: any): any { data[key] = value.map(_cleanData); } else if (_.isObject(value) && value instanceof File) { data[key] = { name: value.name, path: value.path, size: value.size, type: value.type }; + } else if (_.isObject(value) && value instanceof ArrayBuffer) { + window.log.error( + 'Trying to save an ArrayBuffer to the db is most likely an error. This specific field should be removed before the cleanData call' + ); + /// just skip it + continue; } else if (_.isObject(value)) { data[key] = _cleanData(value); } else if (_.isBoolean(value)) { diff --git a/ts/models/message.ts b/ts/models/message.ts index 043e68bca..d16e57701 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -1048,7 +1048,8 @@ export class MessageModel extends Backbone.Model { } perfStart(`messageCommit-${this.attributes.id}`); - const id = await saveMessage(this.attributes); + // because the saving to db calls _cleanData which mutates the field for cleaning, we need to save a copy + const id = await saveMessage(_.cloneDeep(this.attributes)); if (triggerUIUpdate) { this.dispatchMessageUpdate(); } From f84762c889cc160884d8830a85731a71bb7e6e17 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 19 May 2022 16:21:31 +1000 Subject: [PATCH 6/8] fix: attachments download job trying to send function over ipc --- ts/session/utils/AttachmentsDownload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/session/utils/AttachmentsDownload.ts b/ts/session/utils/AttachmentsDownload.ts index 8d2b78df7..940ba2bf9 100644 --- a/ts/session/utils/AttachmentsDownload.ts +++ b/ts/session/utils/AttachmentsDownload.ts @@ -75,7 +75,7 @@ export async function addJob(attachment: any, job: any = {}) { const toSave = { ...job, id, - attachment, + attachment: omit(attachment, ['toJSON']), // when addJob is called from the receiver we get an object with a toJSON call we don't care timestamp, pending: 0, attempts: 0, From 19decad7663a80d2002f0bffa29b838769ed6f95 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 19 May 2022 16:28:31 +1000 Subject: [PATCH 7/8] fix: send read receipt when settings ON and window focused --- .../message/message-item/ReadableMessage.tsx | 9 +++++++++ ts/models/conversation.ts | 13 ++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ts/components/conversation/message/message-item/ReadableMessage.tsx b/ts/components/conversation/message/message-item/ReadableMessage.tsx index 9365a09ff..af0ce7584 100644 --- a/ts/components/conversation/message/message-item/ReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/ReadableMessage.tsx @@ -144,9 +144,18 @@ export const ReadableMessage = (props: ReadableMessageProps) => { const found = await getMessageById(messageId); if (found && Boolean(found.get('unread'))) { + const foundReceivedAt = found.get('received_at'); // mark the message as read. // this will trigger the expire timer. await found.markRead(Date.now()); + + // we should stack those and send them in a single message once every 5secs or something. + // this would be part of an redesign of the sending pipeline + if (foundReceivedAt) { + void getConversationController() + .get(found.id) + ?.sendReadReceiptsIfNeeded([foundReceivedAt]); + } } } } diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index b5860ba01..cfcab74c7 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1146,7 +1146,14 @@ export class ConversationModel extends Backbone.Model { // conversation is viewed, another error message shows up for the contact read = read.filter(item => !item.hasErrors); - if (!this.isPrivate() || !read.length || !options.sendReadReceipts) { + if (read.length && options.sendReadReceipts) { + const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array; + await this.sendReadReceiptsIfNeeded(timestamps); + } + } + + public async sendReadReceiptsIfNeeded(timestamps: Array) { + if (!this.isPrivate() || !timestamps.length) { return; } const settingsReadReceiptEnabled = Storage.get(SettingsKey.settingsReadReceipt) || false; @@ -1154,9 +1161,9 @@ export class ConversationModel extends Backbone.Model { settingsReadReceiptEnabled && !this.isBlocked() && !this.isIncomingRequest(); if (sendReceipt) { - window?.log?.info(`Sending ${read.length} read receipts.`); + window?.log?.info(`Sending ${timestamps.length} read receipts.`); + // we should probably stack read receipts and send them every 5 seconds for instance per conversation - const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array; const receiptMessage = new ReadReceiptMessage({ timestamp: Date.now(), timestamps, From ca0c74317fd9e9e0c313b159187528eb83d95a92 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 19 May 2022 16:40:09 +1000 Subject: [PATCH 8/8] bump to 1.8.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de6eefdc9..20719eb86 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "session-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "version": "1.8.5", + "version": "1.8.6", "license": "GPL-3.0", "author": { "name": "Oxen Labs",