Merge branch 'clearnet' into fix/warn-settings-link-previews

pull/2656/head
tomobre 2 years ago
commit 5edd06a1e5

@ -34,6 +34,7 @@ jobs:
- name: Cache Desktop node_modules
id: cache-desktop-modules
uses: actions/cache@v2
if: runner.os != 'Windows'
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}

@ -31,6 +31,7 @@ jobs:
- name: Cache Desktop node_modules
id: cache-desktop-modules
uses: actions/cache@v2
if: runner.os != 'Windows'
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.10.4",
"version": "1.10.7",
"license": "GPL-3.0",
"author": {
"name": "Oxen Labs",
@ -42,7 +42,11 @@
"@types/react": "17.0.2",
"glob-parent": "^6.0.1",
"got": "^11.8.5",
"jpeg-js": "^0.4.4"
"jpeg-js": "^0.4.4",
"json5": "^2.2.2",
"loader-utils": "^2.0.4",
"terser": "^5.14.2",
"minimatch": "^3.0.5"
},
"scripts": {
"start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod$MULTI electron .",
@ -154,7 +158,7 @@
"uuid": "8.3.2"
},
"devDependencies": {
"@parcel/transformer-sass": "2.5.0",
"@parcel/transformer-sass": "^2.8.3",
"@playwright/test": "1.16.3",
"@types/backbone": "1.4.2",
"@types/better-sqlite3": "7.4.0",
@ -202,6 +206,7 @@
"concurrently": "^7.4.0",
"cross-env": "^6.0.3",
"crypto-browserify": "^3.12.0",
"dmg-builder": "23.6.0",
"electron": "^17.2.0",
"electron-builder": "22.8.0",
"electron-notarize": "^0.2.0",
@ -226,7 +231,7 @@
"node-gyp": "9.0.0",
"node-loader": "^2.0.0",
"nyc": "^15.1.0",
"parcel": "2.5.0",
"parcel": "^2.8.3",
"patch-package": "^6.4.7",
"path-browserify": "^1.0.1",
"playwright": "1.16.3",

@ -129,13 +129,6 @@ textarea {
font-style: normal;
}
.module-message__author-avatar {
position: relative;
margin-inline-end: 20px;
padding-top: 5px;
padding-inline-end: 4px;
}
.module-message--incoming {
margin-inline-start: 0;
margin-inline-end: auto;

@ -25,7 +25,7 @@
position: relative;
display: inline-flex;
flex-direction: row;
align-items: center;
align-items: flex-end;
max-width: 95%;
@media (min-width: 1200px) {

@ -408,7 +408,7 @@ export class SessionConversation extends React.Component<Props, State> {
blob: file,
});
if (blob.blob.size >= MAX_ATTACHMENT_FILESIZE_BYTES) {
if (blob.blob.size > MAX_ATTACHMENT_FILESIZE_BYTES) {
ToastUtils.pushFileSizeErrorAsByte(MAX_ATTACHMENT_FILESIZE_BYTES);
return;
}

@ -181,6 +181,11 @@ const StyledGroupSettingsItem = styled.div`
}
`;
const StyledName = styled.h4`
padding-inline: var(--margins-md);
font-size: var(--font-size-md);
`;
// tslint:disable: cyclomatic-complexity
// tslint:disable: max-func-body-length
export const SessionRightPanelWithDetails = () => {
@ -276,7 +281,7 @@ export const SessionRightPanelWithDetails = () => {
return (
<div className="group-settings">
<HeaderItem />
<h2 data-testid="right-panel-group-name">{displayNameInProfile}</h2>
<StyledName data-testid="right-panel-group-name">{displayNameInProfile}</StyledName>
{showMemberCount && (
<>
<SpacerLG />

@ -8,13 +8,15 @@ import { SessionButtonColor } from '../../../basic/SessionButton';
import { SessionIcon } from '../../../icon';
const StyledTrustSenderUI = styled.div`
padding-inline: var(--margins-sm);
padding-inline: var(--margins-lg);
display: flex;
align-items: center;
width: fit-content;
border-radius: var(--border-radius-message-box);
background-color: var(--message-bubbles-received-background-color);
height: 35px;
margin-left: var(--margins-xs);
`;
const ClickToDownload = styled.div`

@ -150,7 +150,7 @@ export const MessageAttachment = (props: Props) => {
e.stopPropagation();
e.preventDefault();
}}
style={{ padding: '5px 10px' }}
style={{ padding: 'var(--margins-xs) 0px' }}
>
<AudioPlayerWithEncryptedFile
src={firstAttachment.url}

@ -1,5 +1,6 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { OpenGroupData } from '../../../../data/opengroups';
import { MessageRenderingProps } from '../../../../models/messageType';
import { findCachedBlindedMatchOrLookItUp } from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
@ -16,6 +17,13 @@ import {
import { Avatar, AvatarSize, CrownIcon } from '../../../avatar/Avatar';
// tslint:disable: use-simple-attributes
const StyledAvatar = styled.div`
position: relative;
margin-inline-end: 20px;
padding-bottom: 6px;
padding-inline-end: 4px;
`;
export type MessageAvatarSelectorProps = Pick<
MessageRenderingProps,
| 'authorAvatarPath'
@ -23,16 +31,14 @@ export type MessageAvatarSelectorProps = Pick<
| 'sender'
| 'authorProfileName'
| 'isSenderAdmin'
| 'conversationType'
| 'direction'
| 'isPublic'
| 'lastMessageOfSeries'
>;
type Props = { messageId: string };
type Props = { messageId: string; noAvatar: boolean };
export const MessageAvatar = (props: Props) => {
const { messageId } = props;
const { messageId, noAvatar } = props;
const dispatch = useDispatch();
const avatarProps = useSelector(state => getMessageAvatarProps(state as any, messageId));
@ -43,21 +49,21 @@ export const MessageAvatar = (props: Props) => {
if (!avatarProps) {
return null;
}
const {
authorAvatarPath,
authorName,
sender,
authorProfileName,
conversationType,
direction,
isSenderAdmin,
lastMessageOfSeries,
isPublic,
} = avatarProps;
if (conversationType !== 'group' || direction === 'outgoing') {
if (noAvatar) {
return null;
}
const userName = authorName || authorProfileName || sender;
const onMessageAvatarClick = useCallback(async () => {
@ -122,9 +128,9 @@ export const MessageAvatar = (props: Props) => {
}
return (
<div className="module-message__author-avatar" key={`msg-avatar-${sender}`}>
<StyledAvatar key={`msg-avatar-${sender}`}>
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={sender} />
{isSenderAdmin && <CrownIcon />}
</div>
</StyledAvatar>
);
};

@ -11,7 +11,7 @@ import {
isMessageSelectionMode,
} from '../../../../state/selectors/conversations';
import { Reactions } from '../../../../util/reactions';
import { MessageAvatar } from '../message-content/MessageAvatar';
import { MessageAuthorText } from './MessageAuthorText';
import { MessageContent } from './MessageContent';
import { MessageContextMenu } from './MessageContextMenu';
@ -20,7 +20,7 @@ import { MessageStatus } from './MessageStatus';
export type MessageContentWithStatusSelectorProps = Pick<
MessageRenderingProps,
'direction' | 'isDeleted'
'conversationType' | 'direction' | 'isDeleted'
>;
type Props = {
@ -40,7 +40,7 @@ const StyledMessageContentContainer = styled.div<{ direction: 'left' | 'right' }
width: 100%;
${StyledMessageReactions} {
margin-right: var(--margins-sm);
margin-right: var(--margins-md);
}
`;
@ -88,11 +88,13 @@ export const MessageContentWithStatuses = (props: Props) => {
};
const { messageId, ctxMenuID, isDetailView, dataTestId, enableReactions } = props;
if (!contentProps) {
return null;
}
const { direction, isDeleted } = contentProps;
const { conversationType, direction, isDeleted } = contentProps;
const isIncoming = direction === 'incoming';
const noAvatar = conversationType !== 'group' || direction === 'outgoing';
const [popupReaction, setPopupReaction] = useState('');
@ -118,6 +120,7 @@ export const MessageContentWithStatuses = (props: Props) => {
onDoubleClickCapture={onDoubleClickReplyToMessage}
data-testid={dataTestId}
>
<MessageAvatar messageId={messageId} noAvatar={noAvatar} />
<MessageStatus
dataTestId="msg-status-incoming"
messageId={messageId}
@ -125,7 +128,6 @@ export const MessageContentWithStatuses = (props: Props) => {
/>
<StyledMessageWithAuthor isIncoming={isIncoming}>
<MessageAuthorText messageId={messageId} />
<MessageContent messageId={messageId} isDetailView={isDetailView} />
</StyledMessageWithAuthor>
<MessageStatus
@ -148,6 +150,7 @@ export const MessageContentWithStatuses = (props: Props) => {
popupReaction={popupReaction}
setPopupReaction={setPopupReaction}
onPopupClick={handlePopupClick}
noAvatar={noAvatar}
/>
)}
</StyledMessageContentContainer>

@ -15,12 +15,15 @@ import { useSelector } from 'react-redux';
export const popupXDefault = -81;
export const popupYDefault = -90;
const StyledMessageReactionsContainer = styled(Flex)<{ x: number; y: number }>`
const StyledMessageReactionsContainer = styled(Flex)<{ x: number; y: number; noAvatar: boolean }>`
${StyledPopupContainer} {
position: absolute;
top: ${props => `${props.y}px;`};
left: ${props => `${props.x}px;`};
}
// MessageAvatar width + margin-inline-end
${props => !props.noAvatar && 'margin-inline-start: calc(36px + 20px);'}
`;
export const StyledMessageReactions = styled(Flex)<{ fullWidth: boolean }>`
@ -115,13 +118,13 @@ const CompressedReactions = (props: ExpandReactionsProps): ReactElement => {
const ExpandedReactions = (props: ExpandReactionsProps): ReactElement => {
const { handleExpand } = props;
return (
<>
<Flex container={true} flexDirection={'column'} alignItems={'center'} margin="4px 0 0">
<Reactions {...props} />
<StyledReadLess onClick={handleExpand}>
<SessionIcon iconType="chevron" iconSize="medium" iconRotation={180} />
{window.i18n('expandedReactionsText')}
</StyledReadLess>
</>
</Flex>
);
};
@ -139,6 +142,7 @@ type Props = {
onPopupClick?: () => void;
inModal?: boolean;
onSelected?: (emoji: string) => boolean;
noAvatar: boolean;
};
export const MessageReactions = (props: Props): ReactElement => {
@ -151,6 +155,7 @@ export const MessageReactions = (props: Props): ReactElement => {
onPopupClick,
inModal = false,
onSelected,
noAvatar,
} = props;
const [reactions, setReactions] = useState<SortedReactionList>([]);
@ -208,6 +213,7 @@ export const MessageReactions = (props: Props): ReactElement => {
alignItems={inModal ? 'flex-start' : 'center'}
x={popupX}
y={popupY}
noAvatar={noAvatar}
>
{sortedReacts &&
sortedReacts?.length !== 0 &&

@ -16,7 +16,7 @@ import {
} from '../../../../state/selectors/conversations';
import { getIncrement } from '../../../../util/timer';
import { ExpireTimer } from '../../ExpireTimer';
import { MessageAvatar } from '../message-content/MessageAvatar';
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
import { ReadableMessage } from './ReadableMessage';
import styled, { keyframes } from 'styled-components';
@ -238,7 +238,6 @@ export const GenericReadableMessage = (props: Props) => {
isUnread={!!isUnread}
key={`readable-message-${messageId}`}
>
<MessageAvatar messageId={messageId} />
{expirationLength && expirationTimestamp && (
<ExpireTimer
isCorrectSide={!isIncoming}

@ -329,6 +329,7 @@ export const ReactListModal = (props: Props): ReactElement => {
inModal={true}
onSelected={handleSelectedReaction}
onClick={handleReactionClick}
noAvatar={true}
/>
</StyledReactionsContainer>
{reactionsMap && currentReact && (

@ -5,6 +5,7 @@ import styled from 'styled-components';
import { Data } from '../../../data/data';
import { useConversationPropsById, useIsPinned } from '../../../hooks/useParamSelector';
import { getUsBlindedInThatServer } from '../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { CONVERSATION } from '../../../session/constants';
import { UserUtils } from '../../../session/utils';
import {
openConversationToSpecificMessage,
@ -170,7 +171,11 @@ export const ConversationListItemHeaderItem = () => {
@
</MentionAtSymbol>
) : null;
unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
unreadCountDiv = (
<p className="module-conversation-list-item__unread-count">
{unreadCount > CONVERSATION.MAX_UNREAD_COUNT ? `${CONVERSATION.MAX_UNREAD_COUNT}+` : unreadCount}
</p>
);
}
return (

@ -446,10 +446,11 @@ async function getMessageBySenderAndSentAt({
}
async function getMessageByServerId(
conversationId: string,
serverId: number,
skipTimerInit: boolean = false
): Promise<MessageModel | null> {
const message = await channels.getMessageByServerId(serverId);
const message = await channels.getMessageByServerId(conversationId, serverId);
if (!message) {
return null;
}

@ -113,6 +113,8 @@ export async function shutdown() {
// No outstanding jobs, return immediately
if (jobKeys.length === 0) {
window?.log?.info('data.shutdown: No outstanding jobs');
return null;
}
@ -246,6 +248,8 @@ function removeJob(id: number) {
if (_shutdownCallback) {
const keys = Object.keys(jobs);
window?.log?.info(`removeJob: _shutdownCallback and we still have ${keys.length} jobs to run`);
if (keys.length === 0) {
_shutdownCallback();
}

@ -735,7 +735,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
reaction,
sender: UserUtils.getOurPubKeyStrFromCache(),
you: true,
isOpenGroup: false,
});
return;
}
@ -752,7 +751,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
reaction,
sender: UserUtils.getOurPubKeyStrFromCache(),
you: true,
isOpenGroup: false,
});
return;
}

@ -438,6 +438,10 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return undefined;
}
if (this.getConversation()?.get('left')) {
return 'sent';
}
const readBy = this.get('read_by') || [];
if (Storage.get(SettingsKey.settingsReadReceipt) && readBy.length > 0) {
return 'read';
@ -690,7 +694,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
height: height || 0,
path,
fileName,
fileSize: size ? filesize(size) : null,
fileSize: size ? filesize(size, { base: 10 }) : null,
isVoiceMessage: isVoiceMessageBool,
pending: Boolean(pending),
url: path ? getAbsoluteAttachmentPath(path) : '',

@ -1040,10 +1040,14 @@ function getMessageBySenderAndSentAt({ source, sentAt }: { source: string; sentA
return map(rows, row => jsonToObject(row.json));
}
function getMessageByServerId(serverId: number) {
// serverIds are not unique so we need the conversationId
function getMessageByServerId(conversationId: string, serverId: number) {
const row = assertGlobalInstance()
.prepare(`SELECT * FROM ${MESSAGES_TABLE} WHERE serverId = $serverId;`)
.prepare(
`SELECT * FROM ${MESSAGES_TABLE} WHERE conversationId = $conversationId AND serverId = $serverId;`
)
.get({
conversationId,
serverId,
});

@ -626,11 +626,9 @@ async function handleMessageRequestResponse(
unblindedConvoId,
ConversationTypeEnum.PRIVATE
);
let mostRecentActiveAt =
Math.max(...compact(convosToMerge.map(m => m.get('active_at')))) || Date.now();
if (!isFinite(mostRecentActiveAt)) {
mostRecentActiveAt = Date.now();
let mostRecentActiveAt = Math.max(...compact(convosToMerge.map(m => m.get('active_at'))));
if (!isFinite(mostRecentActiveAt) || mostRecentActiveAt <= 0) {
mostRecentActiveAt = toNumber(envelope.timestamp);
}
conversationToApprove.set({

@ -309,7 +309,6 @@ async function handleSwarmMessage(
reaction: rawDataMessage.reaction,
sender: msgModel.get('source'),
you: isUsFromCache(msgModel.get('source')),
isOpenGroup: false,
});
if (
convoToAddMessageTo.isPrivate() &&

@ -38,9 +38,9 @@ export type OpenGroupV2InfoJoinable = OpenGroupV2Info & {
// tslint:disable: no-http-string
const legacyDefaultServerIP = '116.203.70.33';
export const legacyDefaultServerIP = '116.203.70.33';
export const defaultServer = 'https://open.getsession.org';
const defaultServerHost = new window.URL(defaultServer).host;
export const defaultServerHost = new window.URL(defaultServer).host;
/**
* This function returns true if the server url given matches any of the sogs run by Session.

@ -3,7 +3,12 @@ import { ConversationModel } from '../../../../models/conversation';
import { getConversationController } from '../../../conversations';
import { allowOnlyOneAtATime } from '../../../utils/Promise';
import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
import { OpenGroupRequestCommonType } from './ApiUtil';
import {
defaultServer,
defaultServerHost,
legacyDefaultServerIP,
OpenGroupRequestCommonType,
} from './ApiUtil';
import { OpenGroupServerPoller } from './OpenGroupServerPoller';
import _, { clone, isEqual } from 'lodash';
@ -46,9 +51,16 @@ export class OpenGroupManagerV2 {
roomId: string,
publicKey: string
): Promise<ConversationModel | undefined> {
const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${serverUrl}${roomId}`;
// make sure to use the https version of our official sogs
const overridenUrl =
(serverUrl.includes(`://${defaultServerHost}`) && !serverUrl.startsWith('https')) ||
serverUrl.includes(`://${legacyDefaultServerIP}`)
? defaultServer
: serverUrl;
const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${overridenUrl}${roomId}`;
return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
return this.attemptConnectionV2(serverUrl, roomId, publicKey);
return this.attemptConnectionV2(overridenUrl, roomId, publicKey);
});
}
@ -82,10 +94,11 @@ export class OpenGroupManagerV2 {
const poller = this.pollers.get(groupedRoomsServerUrl);
if (!poller) {
const uniqGroupedRooms = _.uniqBy(groupedRooms, r => r.roomId);
this.pollers.set(groupedRoomsServerUrl, new OpenGroupServerPoller(uniqGroupedRooms));
} else {
// this won't do a thing if the room is already polled for
roomInfos.forEach(poller.addRoomToPoll);
groupedRooms.forEach(poller.addRoomToPoll);
}
}
}

@ -2,6 +2,7 @@ import AbortController from 'abort-controller';
import { OpenGroupReactionResponse } from '../../../../types/Reaction';
import { Reactions } from '../../../../util/reactions';
import { OpenGroupRequestCommonType } from '../opengroupV2/ApiUtil';
import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
import {
batchFirstSubIsSuccess,
batchGlobalIsSuccess,
@ -26,7 +27,8 @@ export const clearSogsReactionByServerId = async (
serverId: number,
roomInfos: OpenGroupRequestCommonType
): Promise<boolean> => {
const { supported, conversation } = await hasReactionSupport(serverId);
const converationId = getOpenGroupV2ConversationId(roomInfos.serverUrl, roomInfos.roomId);
const { supported, conversation } = await hasReactionSupport(converationId, serverId);
if (!supported) {
return false;
}
@ -51,7 +53,7 @@ export const clearSogsReactionByServerId = async (
addToMutationCache(cacheEntry);
// Since responses can take a long time we immediately update the moderators's UI and if there is a problem it is overwritten by handleOpenGroupMessageReactions later.
await Reactions.handleClearReaction(serverId, reaction);
await Reactions.handleClearReaction(converationId, serverId, reaction);
const options: Array<OpenGroupBatchRow> = [
{

@ -6,6 +6,7 @@
import { filter, findIndex, remove } from 'lodash';
import { Reactions } from '../../../../util/reactions';
import { OpenGroupReactionMessageV4 } from '../opengroupV2/OpenGroupServerPoller';
import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
export enum ChangeType {
REACTIONS = 0,
@ -143,6 +144,10 @@ export async function processMessagesUsingCache(
}
message.reactions = updatedReactions;
await Reactions.handleOpenGroupMessageReactions(message.reactions, message.id);
await Reactions.handleOpenGroupMessageReactions(
getOpenGroupV2ConversationId(server, room),
message.id,
message.reactions
);
return message;
}

@ -12,19 +12,15 @@ import { Reactions } from '../../../../util/reactions';
import { OnionSending } from '../../../onions/onionSend';
import { ToastUtils, UserUtils } from '../../../utils';
import { OpenGroupPollingUtils } from '../opengroupV2/OpenGroupPollingUtils';
import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
import { getUsBlindedInThatServer } from './knownBlindedkeys';
import { batchGlobalIsSuccess, parseBatchGlobalStatusCode } from './sogsV3BatchPoll';
import {
addToMutationCache,
ChangeType,
SogsV3Mutation,
updateMutationCache,
} from './sogsV3MutationCache';
export const hasReactionSupport = async (
conversationId: string,
serverId: number
): Promise<{ supported: boolean; conversation: ConversationModel | null }> => {
const found = await Data.getMessageByServerId(serverId);
const found = await Data.getMessageByServerId(conversationId, serverId);
if (!found) {
window.log.warn(`Open Group Message ${serverId} not found in db`);
return { supported: false, conversation: null };
@ -57,7 +53,10 @@ export const sendSogsReactionOnionV4 = async (
throw new Error(`Could not find sogs pubkey of url:${serverUrl}`);
}
const { supported, conversation } = await hasReactionSupport(reaction.id);
const { supported, conversation } = await hasReactionSupport(
getOpenGroupV2ConversationId(serverUrl, room),
reaction.id
);
if (!supported) {
return false;
}
@ -82,27 +81,13 @@ export const sendSogsReactionOnionV4 = async (
const method = reaction.action === Action.REACT ? 'PUT' : 'DELETE';
const serverPubkey = allValidRoomInfos[0].serverPublicKey;
const cacheEntry: SogsV3Mutation = {
server: serverUrl,
room: room,
changeType: ChangeType.REACTIONS,
seqno: null,
metadata: {
messageId: reaction.id,
emoji,
action: reaction.action === Action.REACT ? 'ADD' : 'REMOVE',
},
};
addToMutationCache(cacheEntry);
// Since responses can take a long time we immediately update the sender's UI and if there is a problem it is overwritten by handleOpenGroupMessageReactions later.
const me = UserUtils.getOurPubKeyStrFromCache();
await Reactions.handleMessageReaction({
reaction,
sender: blinded ? getUsBlindedInThatServer(conversation) || me : me,
you: true,
isOpenGroup: true,
openGroupConversationId: getOpenGroupV2ConversationId(serverUrl, room),
});
// reaction endpoint requires an empty dict {}
@ -137,9 +122,5 @@ export const sendSogsReactionOnionV4 = async (
const success = Boolean(reaction.action === Action.REACT ? rawMessage.added : rawMessage.removed);
if (success) {
updateMutationCache(cacheEntry, rawMessage.seqno);
}
return success;
};

@ -39,10 +39,14 @@ export const CONVERSATION = {
// Maximum voice message duraton of 5 minutes
// which equates to 1.97 MB
MAX_VOICE_MESSAGE_DURATION: 300,
MAX_UNREAD_COUNT: 9999,
};
// Max attachment size: 6 MB
export const MAX_ATTACHMENT_FILESIZE_BYTES = 6 * 1000 * 1000; // 6MB
/**
* The file server and onion request max upload size is 10MB precisely.
* 10MB is still ok, but one byte more is not.
*/
export const MAX_ATTACHMENT_FILESIZE_BYTES = 10 * 1000 * 1000;
export const VALIDATION = {
MAX_GROUP_NAME_LENGTH: 30,

@ -1,3 +1,5 @@
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../constants';
/**
* This file is used to pad message buffer and attachments
*/
@ -73,10 +75,17 @@ export function addAttachmentPadding(data: ArrayBuffer): ArrayBuffer {
const originalUInt = new Uint8Array(data);
window?.log?.info('Adding attachment padding...');
const paddedSize = Math.max(
let paddedSize = Math.max(
541,
Math.floor(Math.pow(1.05, Math.ceil(Math.log(originalUInt.length) / Math.log(1.05))))
);
if (
paddedSize > MAX_ATTACHMENT_FILESIZE_BYTES &&
originalUInt.length <= MAX_ATTACHMENT_FILESIZE_BYTES
) {
paddedSize = MAX_ATTACHMENT_FILESIZE_BYTES;
}
const paddedData = new ArrayBuffer(paddedSize);
const paddedUInt = new Uint8Array(paddedData);

@ -1129,7 +1129,7 @@ export const getMessageContentWithStatusesSelectorProps = createSelector(
}
const msgProps: MessageContentWithStatusSelectorProps = {
...pick(props.propsForMessage, ['direction', 'isDeleted']),
...pick(props.propsForMessage, ['conversationType', 'direction', 'isDeleted']),
};
return msgProps;

@ -10,6 +10,7 @@ import {
getUnpaddedAttachment,
removeMessagePadding,
} from '../../../../session/crypto/BufferPadding';
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../../../session/constants';
chai.use(chaiAsPromised as any);
chai.should();
@ -30,6 +31,41 @@ describe('Padding', () => {
);
});
it('no padding if attachment has the max size', () => {
//if the attachment is already of the max size, we do not pad it more
const bufferIn = new Uint8Array(MAX_ATTACHMENT_FILESIZE_BYTES);
const paddedBuffer = addAttachmentPadding(bufferIn);
expect(paddedBuffer.byteLength).to.equal(MAX_ATTACHMENT_FILESIZE_BYTES);
expect(new Uint8Array(paddedBuffer)).to.equalBytes(bufferIn);
});
it('add padding is limited to max attachment size', () => {
// there is only enough room to add one byte as padding.
const bufferIn = new Uint8Array(MAX_ATTACHMENT_FILESIZE_BYTES - 1);
const paddedBuffer = addAttachmentPadding(bufferIn);
expect(paddedBuffer.byteLength).to.equal(MAX_ATTACHMENT_FILESIZE_BYTES);
expect(new Uint8Array(paddedBuffer.slice(0, bufferIn.length))).to.equalBytes(bufferIn);
// this makes sure that the padding is just the 0 bytes
expect(paddedBuffer.slice(bufferIn.length).byteLength).to.eq(1);
expect(new Uint8Array(paddedBuffer.slice(bufferIn.length))).to.equalBytes(
new Uint8Array([0])
);
});
it('add padding if the attachment is already too big', () => {
// we just want to make sure we do not overide attachment data. The file upload will fail, but at least make sure to keep the user data.
const bufferIn = new Uint8Array(MAX_ATTACHMENT_FILESIZE_BYTES + 1);
const paddedBuffer = addAttachmentPadding(bufferIn);
const expectedPaddedSize = Math.floor(
Math.pow(1.05, Math.ceil(Math.log(bufferIn.length) / Math.log(1.05)))
);
expect(new Uint8Array(paddedBuffer.slice(0, bufferIn.length))).to.equalBytes(bufferIn);
// this makes sure that the padding is just the 0 bytes
expect(new Uint8Array(paddedBuffer.slice(bufferIn.length))).to.equalBytes(
new Uint8Array(expectedPaddedSize - bufferIn.length)
);
});
it('remove padding', () => {
// padding can be anything after the expected size
const expectedSize = 10;

@ -56,7 +56,6 @@ describe('ReactionMessage', () => {
reaction: reaction as SignalService.DataMessage.IReaction,
sender: ourNumber,
you: true,
isOpenGroup: false,
});
expect(updatedMessage?.get('reacts'), 'original message should have reacts').to.not.be
@ -89,7 +88,6 @@ describe('ReactionMessage', () => {
reaction: reaction as SignalService.DataMessage.IReaction,
sender: ourNumber,
you: true,
isOpenGroup: false,
});
expect(updatedMessage?.get('reacts'), 'original message reacts should be undefined').to.be

@ -38,14 +38,14 @@ function hitRateLimit(): boolean {
*/
const getMessageByReaction = async (
reaction: SignalService.DataMessage.IReaction,
isOpenGroup: boolean
openGroupConversationId?: string
): Promise<MessageModel | null> => {
let originalMessage = null;
const originalMessageId = Number(reaction.id);
const originalMessageAuthor = reaction.author;
if (isOpenGroup) {
originalMessage = await Data.getMessageByServerId(originalMessageId);
if (openGroupConversationId && !isEmpty(openGroupConversationId)) {
originalMessage = await Data.getMessageByServerId(openGroupConversationId, originalMessageId);
} else {
const collection = await Data.getMessagesBySentAt(originalMessageId);
originalMessage = collection.find((item: MessageModel) => {
@ -152,19 +152,19 @@ const handleMessageReaction = async ({
reaction,
sender,
you,
isOpenGroup,
openGroupConversationId,
}: {
reaction: SignalService.DataMessage.IReaction;
sender: string;
you: boolean;
isOpenGroup: boolean;
openGroupConversationId?: string;
}) => {
if (!reaction.emoji) {
window?.log?.warn(`There is no emoji for the reaction ${reaction}.`);
return;
}
const originalMessage = await getMessageByReaction(reaction, isOpenGroup);
const originalMessage = await getMessageByReaction(reaction, openGroupConversationId);
if (!originalMessage) {
return;
}
@ -240,10 +240,12 @@ const handleMessageReaction = async ({
* Handles updating the UI when clearing all reactions for a certain emoji
* Only usable by moderators in opengroups and runs on their client
*/
const handleClearReaction = async (serverId: number, emoji: string) => {
const originalMessage = await Data.getMessageByServerId(serverId);
const handleClearReaction = async (conversationId: string, serverId: number, emoji: string) => {
const originalMessage = await Data.getMessageByServerId(conversationId, serverId);
if (!originalMessage) {
window?.log?.warn(`Cannot find the original reacted message ${serverId}.`);
window?.log?.warn(
`Cannot find the original reacted message ${serverId} in conversation ${conversationId}.`
);
return;
}
@ -265,14 +267,18 @@ const handleClearReaction = async (serverId: number, emoji: string) => {
/**
* Handles all message reaction updates/responses for opengroups
* serverIds are not unique so we need the conversationId
*/
const handleOpenGroupMessageReactions = async (
reactions: OpenGroupReactionList,
serverId: number
conversationId: string,
serverId: number,
reactions: OpenGroupReactionList
) => {
const originalMessage = await Data.getMessageByServerId(serverId);
const originalMessage = await Data.getMessageByServerId(conversationId, serverId);
if (!originalMessage) {
window?.log?.warn(`Cannot find the original reacted message ${serverId}.`);
window?.log?.warn(
`Cannot find the original reacted message ${serverId} in conversation ${conversationId}.`
);
return;
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save