diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c337220a5a22..70547738149e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -311,6 +311,10 @@ "message": "You are no longer a member of the group.", "description": "Displayed when a user can't send a message because they have left the group" }, + "invalidConversation": { + "message": "This group is invalid. Please create a new group.", + "description": "Displayed when a user can't send a message because something has gone wrong in the conversation." + }, "scrollDown": { "message": "Scroll to bottom of conversation", "description": "Alt text for button to take user down to bottom of conversation, shown when user scrolls up" diff --git a/ts/groups.ts b/ts/groups.ts index d30b07f29552..ec727a0742ed 100644 --- a/ts/groups.ts +++ b/ts/groups.ts @@ -163,6 +163,8 @@ type UpdatesResultType = { // Constants export const MASTER_KEY_LENGTH = 32; +export const ID_V1_LENGTH = 16; +export const ID_LENGTH = 32; const TEMPORAL_AUTH_REJECTED_CODE = 401; const GROUP_ACCESS_DENIED_CODE = 403; const SUPPORTED_CHANGE_EPOCH = 0; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 61856296d2a5..eb6c5d30636a 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -266,7 +266,8 @@ export class ConversationModel extends window.Backbone.Model< return false; } - return fromEncodedBinaryToArrayBuffer(groupId).byteLength === 16; + const buffer = fromEncodedBinaryToArrayBuffer(groupId); + return buffer.byteLength === window.Signal.Groups.ID_V1_LENGTH; } isGroupV2(): boolean { @@ -277,7 +278,10 @@ export class ConversationModel extends window.Backbone.Model< const groupVersion = this.get('groupVersion') || 0; - return groupVersion === 2 && base64ToArrayBuffer(groupId).byteLength === 32; + return ( + groupVersion === 2 && + base64ToArrayBuffer(groupId).byteLength === window.Signal.Groups.ID_LENGTH + ); } isMemberPending(conversationId: string): boolean { @@ -822,6 +826,10 @@ export class ConversationModel extends window.Backbone.Model< }); } + isValid(): boolean { + return this.isPrivate() || this.isGroupV1() || this.isGroupV2(); + } + maybeRepairGroupV2(data: { masterKey: string; secretParams: string; diff --git a/ts/views/conversation_view.ts b/ts/views/conversation_view.ts index 2f2c77be3db5..f74b0b516d47 100644 --- a/ts/views/conversation_view.ts +++ b/ts/views/conversation_view.ts @@ -76,6 +76,12 @@ Whisper.LeftGroupToast = Whisper.ToastView.extend({ }, }); +Whisper.InvalidConversationToast = Whisper.ToastView.extend({ + render_attributes() { + return { toastMessage: window.i18n('invalidConversation') }; + }, +}); + Whisper.OriginalNotFoundToast = Whisper.ToastView.extend({ render_attributes() { return { toastMessage: window.i18n('originalMessageNotFound') }; @@ -2897,6 +2903,10 @@ Whisper.ConversationView = Whisper.View.extend({ this.model.set({ profileSharing: true }); } + if (this.showInvalidMessageToast()) { + return; + } + const { packId, stickerId } = options; this.model.sendStickerMessage(packId, stickerId); } catch (error) { @@ -3030,6 +3040,43 @@ Whisper.ConversationView = Whisper.View.extend({ }); }, + showInvalidMessageToast(messageText?: string): boolean { + let ToastView; + + if (window.reduxStore.getState().expiration.hasExpired) { + ToastView = Whisper.ExpiredToast; + } + if (!this.model.isValid()) { + ToastView = Whisper.InvalidConversationToast; + } + if ( + this.model.isPrivate() && + (window.storage.isBlocked(this.model.get('e164')) || + window.storage.isUuidBlocked(this.model.get('uuid'))) + ) { + ToastView = Whisper.BlockedToast; + } + if ( + !this.model.isPrivate() && + window.storage.isGroupBlocked(this.model.get('groupId')) + ) { + ToastView = Whisper.BlockedGroupToast; + } + if (!this.model.isPrivate() && this.model.get('left')) { + ToastView = Whisper.LeftGroupToast; + } + if (messageText && messageText.length > MAX_MESSAGE_BODY_LENGTH) { + ToastView = Whisper.MessageBodyTooLongToast; + } + + if (ToastView) { + this.showToast(ToastView); + return true; + } + + return false; + }, + async sendMessage(message = '', mentions = [], options = {}) { this.sendStart = Date.now(); @@ -3058,32 +3105,7 @@ Whisper.ConversationView = Whisper.View.extend({ this.model.clearTypingTimers(); - let ToastView; - if (window.reduxStore.getState().expiration.hasExpired) { - ToastView = Whisper.ExpiredToast; - } - if ( - this.model.isPrivate() && - (window.storage.isBlocked(this.model.get('e164')) || - window.storage.isUuidBlocked(this.model.get('uuid'))) - ) { - ToastView = Whisper.BlockedToast; - } - if ( - !this.model.isPrivate() && - window.storage.isGroupBlocked(this.model.get('groupId')) - ) { - ToastView = Whisper.BlockedGroupToast; - } - if (!this.model.isPrivate() && this.model.get('left')) { - ToastView = Whisper.LeftGroupToast; - } - if (message.length > MAX_MESSAGE_BODY_LENGTH) { - ToastView = Whisper.MessageBodyTooLongToast; - } - - if (ToastView) { - this.showToast(ToastView); + if (this.showInvalidMessageToast(message)) { this.enableMessageField(); return; } diff --git a/ts/window.d.ts b/ts/window.d.ts index da3fabd5157e..ef2aeb1293f0 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -674,28 +674,30 @@ export type WhisperType = { deliveryReceiptBatcher: BatcherType; RotateSignedPreKeyListener: WhatIsThis; - ExpiredToast: typeof Whisper.ToastView; - BlockedToast: typeof Whisper.ToastView; BlockedGroupToast: typeof Whisper.ToastView; - LeftGroupToast: typeof Whisper.ToastView; - OriginalNotFoundToast: typeof Whisper.ToastView; - OriginalNoLongerAvailableToast: typeof Whisper.ToastView; + BlockedToast: typeof Whisper.ToastView; + CannotMixImageAndNonImageAttachmentsToast: typeof Whisper.ToastView; + DangerousFileTypeToast: typeof Whisper.ToastView; + ExpiredToast: typeof Whisper.ToastView; + FileSavedToast: typeof Whisper.ToastView; + FileSizeToast: any; FoundButNotLoadedToast: typeof Whisper.ToastView; - VoiceNoteLimit: typeof Whisper.ToastView; - VoiceNoteMustBeOnlyAttachmentToast: typeof Whisper.ToastView; + InvalidConversationToast: typeof Whisper.ToastView; + LeftGroupToast: typeof Whisper.ToastView; + MaxAttachmentsToast: typeof Whisper.ToastView; + MessageBodyTooLongToast: typeof Whisper.ToastView; + OneNonImageAtATimeToast: typeof Whisper.ToastView; + OriginalNoLongerAvailableToast: typeof Whisper.ToastView; + OriginalNotFoundToast: typeof Whisper.ToastView; + PinnedConversationsFullToast: typeof Whisper.ToastView; + ReactionFailedToast: typeof Whisper.ToastView; TapToViewExpiredIncomingToast: typeof Whisper.ToastView; TapToViewExpiredOutgoingToast: typeof Whisper.ToastView; - FileSavedToast: typeof Whisper.ToastView; - ReactionFailedToast: typeof Whisper.ToastView; - MessageBodyTooLongToast: typeof Whisper.ToastView; - FileSizeToast: any; - UnableToLoadToast: typeof Whisper.ToastView; - DangerousFileTypeToast: typeof Whisper.ToastView; - OneNonImageAtATimeToast: typeof Whisper.ToastView; - CannotMixImageAndNonImageAttachmentsToast: typeof Whisper.ToastView; - MaxAttachmentsToast: typeof Whisper.ToastView; TimerConflictToast: typeof Whisper.ToastView; - PinnedConversationsFullToast: typeof Whisper.ToastView; + UnableToLoadToast: typeof Whisper.ToastView; + VoiceNoteLimit: typeof Whisper.ToastView; + VoiceNoteMustBeOnlyAttachmentToast: typeof Whisper.ToastView; + ConversationLoadingScreen: typeof Whisper.View; ConversationView: typeof Whisper.View; View: typeof Backbone.View;