Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d533be8
feat: allow serverPubkey to be provided for incoming attachments
Bilb Sep 29, 2025
cf2135e
chore: clean up quoted attachment unused
Bilb Oct 3, 2025
fa54130
feat: link deterministic decrypt fron libsession
Bilb Oct 3, 2025
9b32fad
feat: support download from other fs and add deterministic encrypt
Bilb Oct 9, 2025
8aa606c
chore: remove pnserver code (all server side now)
Bilb Oct 9, 2025
df5cf98
fix: use correct domain when uploading our avatar
Bilb Oct 9, 2025
4958169
fix: remove default fs pk from outgoing msg
Bilb Oct 9, 2025
ddde02a
feat: add timeout to avatar processing
Bilb Oct 13, 2025
6a8b161
chore: bump libsession-util-nodejs to 0.5.9
Bilb Oct 13, 2025
068ea17
fix: use first part of the locale with _/- in it as a full locale
Bilb Oct 13, 2025
985d9fe
Merge pull request #1635 from session-foundation/fix-locale-underscor…
Bilb Oct 13, 2025
5cfa342
Merge remote-tracking branch 'upstream/dev' into feat/attachments-cha…
Bilb Oct 13, 2025
a5c9738
chore: cleanup PR before review
Bilb Oct 13, 2025
a86f529
Merge pull request #1632 from session-foundation/feat/attachments-cha…
Bilb Oct 16, 2025
922fb2d
fix: add supersuper fileserver for testing
Bilb Oct 17, 2025
502406e
Merge pull request #1640 from session-foundation/fix-attachment-decry…
Bilb Oct 17, 2025
2f7b64d
fix: do not add default fs pk and empty url fragment
Bilb Oct 22, 2025
6ae0284
fix: display fileId in messageInfo
Bilb Oct 22, 2025
3df4703
chore: added some debug tools to show attachment details
Bilb Oct 22, 2025
65f402b
fix: renew now cares about if the fs ttl was 30s
Bilb Oct 23, 2025
b84ba43
chore: clean up headers needed for short fs ttl
Bilb Oct 23, 2025
c37ff42
Merge pull request #1644 from session-foundation/fix-qa-issues-fs-cha…
Bilb Oct 23, 2025
9e07fb2
fix: allow to copy selected text on copy menu item
Bilb Oct 26, 2025
75e79e6
Merge pull request #1654 from session-foundation/fix-allow-copy-selec…
Bilb Oct 26, 2025
4566a98
fix: save attachment url on sending side too
Bilb Oct 27, 2025
673c115
feat: fallback to original gif if conversion takes too long
Bilb Oct 28, 2025
f2cc0ea
Merge pull request #1661 from session-foundation/fix-fs-changes-qa-2
Bilb Oct 28, 2025
383a0a7
fix: allow some sharp errors to bubbleup and be displayed to the user
Bilb Oct 29, 2025
f9e0485
Merge pull request #1664 from session-foundation/fix-fs-changes-qa-3
Bilb Oct 29, 2025
b70a400
fix: make sure we iterate over qualities for img resize
Bilb Oct 29, 2025
ceacd5b
Merge pull request #1666 from session-foundation/fix-fs-changes-qa-4
Bilb Oct 29, 2025
4b0ff9d
fix: first attempt with gif is to convert to webp
Bilb Oct 30, 2025
84d6562
Merge remote-tracking branch 'upstream/master' into dev
Bilb Oct 30, 2025
5b53ebd
fix: make sure NTS priority is set when unhiding it too
Bilb Oct 30, 2025
5d0e724
fix: track reupload needed for incoming avatar for us
Bilb Oct 30, 2025
132737b
Merge pull request #1669 from session-foundation/fix-download-avatar-…
Bilb Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"fs-extra": "11.3.0",
"glob": "10.4.5",
"image-type": "^4.1.0",
"libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.5.8/libsession_util_nodejs-v0.5.8.tar.gz",
"libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.5.9/libsession_util_nodejs-v0.5.9.tar.gz",
"libsodium-wrappers-sumo": "^0.7.15",
"linkify-it": "^5.0.0",
"lodash": "^4.17.21",
Expand Down
1 change: 1 addition & 0 deletions preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ window.sessionFeatureFlags = {
replaceLocalizedStringsWithKeys: false,
// Hooks
useClosedGroupV2QAButtons: false, // TODO DO NOT MERGE
useDeterministicEncryption: !isEmpty(process.env.SESSION_ATTACH_DETERMINISTIC_ENCRYPTION),
useOnionRequests: true,
useTestNet: isTestNet() || isTestIntegration(),
useLocalDevNet: !isEmpty(process.env.LOCAL_DEVNET_SEED_URL)
Expand Down
36 changes: 18 additions & 18 deletions protos/SignalService.proto
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,11 @@ message DataMessage {
}

message Quote {

message QuotedAttachment {
optional string contentType = 1;
optional string fileName = 2;
optional AttachmentPointer thumbnail = 3;
}
reserved 3, 4;
reserved "text", "attachments";

required uint64 id = 1;
required string author = 2;
optional string text = 3;
repeated QuotedAttachment attachments = 4;
}

message Preview {
Expand Down Expand Up @@ -268,16 +262,22 @@ message AttachmentPointer {

// @required
required fixed64 deprecated_id = 1;
optional string contentType = 2;
optional bytes key = 3;
optional uint32 size = 4;
optional bytes digest = 6;
optional string fileName = 7;
optional uint32 flags = 8;
optional uint32 width = 9;
optional uint32 height = 10;
optional string caption = 11;
optional string url = 101;
optional string contentType = 2;
optional bytes key = 3;
optional uint32 size = 4;
optional bytes digest = 6;
optional string fileName = 7;
optional uint32 flags = 8;
optional uint32 width = 9;
optional uint32 height = 10;
optional string caption = 11;
/**
* This field can be just an url to the file, or have a fragment appended to it that can contain:
* - `p=<server_pubkey_hex>` // hex encoded pubkey of the file server
* - `d=` // if the file is deterministically encrypted, this field is present, otherwise it is not
* If needed, those fields are a &, and can be parsed/built with the usual URLSearchParams logic
*/
optional string url = 101;
}


Expand Down
4 changes: 2 additions & 2 deletions ts/components/conversation/composition/CompositionBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ export interface StagedLinkPreviewData {
scaledDown: ProcessedLinkPreviewThumbnailType | null;
}

export interface StagedAttachmentType extends AttachmentType {
export type StagedAttachmentType = AttachmentType & {
file: File;
path?: string; // a bit hacky, but this is the only way to make our sending audio message be playable, this must be used only for those message
}
};

export type SendMessageType = {
conversationId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ export const MessageContextMenu = (props: Props) => {
}, [isSelectedBlocked, messageId]);

const copyText = useCallback(() => {
MessageInteraction.copyBodyToClipboard(text);
const selection = window.getSelection();
const selectedText = selection?.toString().trim();
// Note: we want to allow to copy through the "Copy" menu item the currently selected text, if any.
MessageInteraction.copyBodyToClipboard(selectedText || text);
}, [text]);

const onSelect = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,12 @@ async function getPropsForMessageInfo(
const found = await Data.getMessageById(messageId);
const attachmentsWithMediaDetails: Array<PropsForAttachment> = [];
if (found) {
const attachmentsInMsg = found.get('attachments') || [];

// process attachments so we have the fileSize, url and screenshots
for (let i = 0; i < attachments.length; i++) {
const props = found.getPropsForAttachment(attachments[i]);
const fsUrl = attachmentsInMsg?.[i].url;
if (
props?.contentType &&
GoogleChrome.isVideoTypeSupported(props?.contentType) &&
Expand All @@ -134,6 +137,7 @@ async function getPropsForMessageInfo(
attachmentsWithMediaDetails.push({
...props,
duration,
url: fsUrl,
});
} else if (props?.contentType && isAudio(props.contentType) && !props.duration && props.url) {
// eslint-disable-next-line no-await-in-loop
Expand All @@ -145,9 +149,10 @@ async function getPropsForMessageInfo(
attachmentsWithMediaDetails.push({
...props,
duration,
url: fsUrl,
});
} else if (props) {
attachmentsWithMediaDetails.push(props);
attachmentsWithMediaDetails.push({ ...props, url: fsUrl });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { PropsForAttachment } from '../../../../../../state/ducks/conversations'
import { Flex } from '../../../../../basic/Flex';
import { tr } from '../../../../../../localization/localeTools';
import { saveLogToDesktop } from '../../../../../../util/logger/renderer_process_logging';
import { extractDetailsFromUrlFragment } from '../../../../../../session/url';
import { isDevProd } from '../../../../../../shared/env_vars';

type Props = {
attachment: PropsForAttachment;
Expand All @@ -16,7 +18,7 @@ const StyledLabelContainer = styled(Flex)`
}
`;

function formatAttachmentUrl(attachment: PropsForAttachment) {
function formatAttachmentUrl(attachment: Pick<PropsForAttachment, 'url'>) {
// Note: desktop overwrites the url with the local path once the file is downloaded,
// and I think this is how we know the file was downloaded.

Expand All @@ -28,7 +30,8 @@ function formatAttachmentUrl(attachment: PropsForAttachment) {
return tr('attachmentsNa');
}

const fileId = attachment.url.split('/').pop() || '';
const fileUrl = URL.canParse(attachment.url) && new URL(attachment.url);
const fileId = fileUrl ? fileUrl?.pathname.split('/').pop() || '' : '';

if (!fileId) {
return tr('attachmentsNa');
Expand All @@ -37,12 +40,23 @@ function formatAttachmentUrl(attachment: PropsForAttachment) {
return fileId;
}

function extractAttachmentDetails(attachment: Pick<PropsForAttachment, 'url'>) {
const fileUrl = URL.canParse(attachment?.url) && new URL(attachment.url);
return {
deterministicEncryption:
(fileUrl && extractDetailsFromUrlFragment(fileUrl)?.deterministicEncryption) || false,
fsHost: fileUrl ? fileUrl.hostname : tr('attachmentsNa'),
};
}

export const AttachmentInfo = (props: Props) => {
const { attachment } = props;

// NOTE the attachment.url will be an empty string if the attachment is broken
const hasError = attachment.error || attachment.url === '';

const { deterministicEncryption, fsHost } = extractAttachmentDetails(attachment);

return (
<Flex $container={true} $flexDirection="column" $flexGap="var(--margins-xs)">
<LabelWithInfo label={tr('attachmentsFileId')} info={formatAttachmentUrl(attachment)} />
Expand Down Expand Up @@ -78,6 +92,15 @@ export const AttachmentInfo = (props: Props) => {
}}
/>
) : null}
{isDevProd() ? (
<>
<LabelWithInfo
label="Uses Deterministic Encryption"
info={deterministicEncryption ? 'Yes' : 'No'}
/>
<LabelWithInfo label="Fs host" info={fsHost} />
</>
) : null}
</StyledLabelContainer>
</Flex>
);
Expand Down
8 changes: 5 additions & 3 deletions ts/components/dialog/EditProfilePictureModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const EditProfilePictureModal = ({ conversationId }: EditProfilePictureMo
const ourAvatarIsUploading = useOurAvatarIsUploading();
const ourAvatarUploadFailed = useOurAvatarUploadFailed();
const sogsAvatarIsUploading = useAvatarOfRoomIsUploading(conversationId);
const [isProcessing, setIsProcessing] = useState(false);

const [newAvatarObjectUrl, setNewAvatarObjectUrl] = useState<string | null>(avatarPath);
const [isNewAvatarAnimated, setIsNewAvatarAnimated] = useState<boolean>(false);
Expand Down Expand Up @@ -169,7 +170,7 @@ export const EditProfilePictureModal = ({ conversationId }: EditProfilePictureMo
const isPublic = useIsPublic(conversationId);

const handleAvatarClick = async () => {
const res = await pickFileForAvatar();
const res = await pickFileForAvatar(setIsProcessing);

if (!res) {
window.log.error('Failed to pick avatar');
Expand Down Expand Up @@ -210,7 +211,8 @@ export const EditProfilePictureModal = ({ conversationId }: EditProfilePictureMo
await triggerUploadProfileAvatar(newAvatarObjectUrl, conversationId, dispatch);
};

const loading = ourAvatarIsUploading || groupAvatarChangePending || sogsAvatarIsUploading;
const loading =
ourAvatarIsUploading || groupAvatarChangePending || sogsAvatarIsUploading || isProcessing;

const newAvatarLoaded = newAvatarObjectUrl !== avatarPath;

Expand Down Expand Up @@ -328,7 +330,7 @@ export const EditProfilePictureModal = ({ conversationId }: EditProfilePictureMo
{loading ? (
<>
<SpacerSM />
{isMe ? <Localizer token="updating" /> : null}
{isMe && !isProcessing ? <Localizer token="updating" /> : null}
<SessionSpinner loading={loading} height="30px" />
</>
) : (
Expand Down
32 changes: 3 additions & 29 deletions ts/components/leftpane/ActionsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { DecryptedAttachmentsManager } from '../../session/crypto/DecryptedAttac

import { DURATION } from '../../session/constants';

import { reuploadCurrentAvatarUs } from '../../interactions/avatar-interactions/nts-avatar-interactions';
import {
onionPathModal,
updateDebugMenuModal,
Expand Down Expand Up @@ -52,14 +51,13 @@ import { useDebugMode } from '../../state/selectors/debug';
import { networkDataActions } from '../../state/ducks/networkData';
import { LUCIDE_ICONS_UNICODE } from '../icon/lucide';
import { AvatarMigrate } from '../../session/utils/job_runners/jobs/AvatarMigrateJob';
import { NetworkTime } from '../../util/NetworkTime';
import { Storage } from '../../util/storage';
import { getFileInfoFromFileServer } from '../../session/apis/file_server_api/FileServerApi';
import { themesArray } from '../../themes/constants/colors';
import { isDebugMode, isDevProd } from '../../shared/env_vars';
import { GearAvatarButton } from '../buttons/avatar/GearAvatarButton';
import { useZoomShortcuts } from '../../hooks/useZoomingShortcut';
import { OnionStatusLight } from '../dialog/OnionStatusPathDialog';
import { AvatarReupload } from '../../session/utils/job_runners/jobs/AvatarReuploadJob';

const StyledContainerAvatar = styled.div`
padding: var(--margins-lg);
Expand Down Expand Up @@ -98,17 +96,6 @@ const triggerSyncIfNeeded = async () => {
}
};

const triggerAvatarReUploadIfNeeded = async () => {
const lastAvatarUploadExpiryMs =
(await Data.getItemById(SettingsKey.ntsAvatarExpiryMs))?.value || Number.MAX_SAFE_INTEGER;

if (NetworkTime.now() > lastAvatarUploadExpiryMs) {
window.log.info('Reuploading avatar...');
// reupload the avatar
await reuploadCurrentAvatarUs();
}
};

/**
* This function is called only once: on app startup with a logged in user
*/
Expand All @@ -127,9 +114,8 @@ const doAppStartUp = async () => {
}); // refresh our swarm on start to speed up the first message fetching event
void Data.cleanupOrphanedAttachments();

// TODOLATER make this a job of the JobRunner
// Note: do not make this a debounce call (as for some reason it doesn't work with promises)
void triggerAvatarReUploadIfNeeded();
await AvatarReupload.addAvatarReuploadJob();

/* Postpone a little bit of the polling of sogs messages to let the swarm messages come in first. */
global.setTimeout(() => {
Expand All @@ -147,17 +133,6 @@ const doAppStartUp = async () => {
// Schedule a confSyncJob in some time to let anything incoming from the network be applied and see if there is a push needed
// Note: this also starts periodic jobs, so we don't need to keep doing it
await UserSync.queueNewJobIfNeeded();

// on app startup, check that the avatar expiry on the file server
const avatarPointer = ConvoHub.use()
.get(UserUtils.getOurPubKeyStrFromCache())
.getAvatarPointer();
if (avatarPointer) {
const details = await getFileInfoFromFileServer(avatarPointer);
if (details?.expiryMs) {
await Storage.put(SettingsKey.ntsAvatarExpiryMs, details.expiryMs);
}
}
}, 20000);

global.setTimeout(() => {
Expand Down Expand Up @@ -283,8 +258,7 @@ export const ActionsPanel = () => {
if (!ourPrimaryConversation) {
return;
}
// this won't be run every days, but if the app stays open for more than 10 days
void triggerAvatarReUploadIfNeeded();
void AvatarReupload.addAvatarReuploadJob();
},
window.sessionFeatureFlags.fsTTL30s ? DURATION.SECONDS * 1 : DURATION.DAYS * 1
);
Expand Down
2 changes: 0 additions & 2 deletions ts/data/settings-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const settingsOpengroupPruning = 'prune-setting';
const settingsNotification = 'notification-setting';
const settingsAudioNotification = 'audio-notification-setting';
const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
const ntsAvatarExpiryMs = 'ntsAvatarExpiryMs';
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
const hasFollowSystemThemeEnabled = 'hasFollowSystemThemeEnabled';
const hideRecoveryPassword = 'hideRecoveryPassword';
Expand Down Expand Up @@ -44,7 +43,6 @@ export const SettingsKey = {
settingsNotification,
settingsAudioNotification,
hasSyncedInitialConfigurationItem,
ntsAvatarExpiryMs,
hasLinkPreviewPopupBeenDisplayed,
latestUserProfileEnvelopeTimestamp,
latestUserGroupEnvelopeTimestamp,
Expand Down
Loading
Loading