Skip to content

Commit 09310ab

Browse files
committed
fix: fetch nts avatar expiry on start up
1 parent 3909f10 commit 09310ab

File tree

6 files changed

+104
-20
lines changed

6 files changed

+104
-20
lines changed

preload.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ window.sessionFeatureFlags = {
6767
showPopoverAnchors: false,
6868
proAvailable: !isEmpty(process.env.SESSION_PRO),
6969
mockUserHasPro: !isEmpty(process.env.SESSION_HAS_PRO),
70-
fsTTL30s: !isEmpty(process.env.FILE_SERVER_TTL_30S),
70+
fsTTL30s: !isEmpty(process.env.FILE_SERVER_TTL_30S), // Note: some stuff are init when the app start, so this flag should only be set from the env itself.
7171
debug: {
7272
debugLogging: !isEmpty(process.env.SESSION_DEBUG),
7373
debugLibsessionDumps: !isEmpty(process.env.SESSION_DEBUG_LIBSESSION_DUMPS),

ts/components/leftpane/ActionsPanel.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ import { networkDataActions } from '../../state/ducks/networkData';
6161
import { searchActions } from '../../state/ducks/search';
6262
import { LUCIDE_ICONS_UNICODE } from '../icon/lucide';
6363
import { AvatarMigrate } from '../../session/utils/job_runners/jobs/AvatarMigrateJob';
64+
import { NetworkTime } from '../../util/NetworkTime';
65+
import { Storage } from '../../util/storage';
66+
import { getFileInfoFromFileServer } from '../../session/apis/file_server_api/FileServerApi';
6467

6568
const StyledContainerAvatar = styled.div`
6669
padding: var(--margins-lg);
@@ -200,10 +203,10 @@ const triggerSyncIfNeeded = async () => {
200203
};
201204

202205
const triggerAvatarReUploadIfNeeded = async () => {
203-
const lastTimeStampAvatarUpload =
204-
(await Data.getItemById(SettingsKey.lastAvatarUploadTimestamp))?.value || 0;
206+
const lastAvatarUploadExpiryMs =
207+
(await Data.getItemById(SettingsKey.ntsAvatarExpiryMs))?.value || 0;
205208

206-
if (Date.now() - lastTimeStampAvatarUpload > DURATION.DAYS * 14) {
209+
if (NetworkTime.now() > lastAvatarUploadExpiryMs) {
207210
window.log.info('Reuploading avatar...');
208211
// reupload the avatar
209212
await reuploadCurrentAvatarUs();
@@ -243,10 +246,22 @@ const doAppStartUp = async () => {
243246
void MessageQueue.use().processAllPending();
244247
}, 3000);
245248

246-
global.setTimeout(() => {
249+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
250+
global.setTimeout(async () => {
247251
// Schedule a confSyncJob in some time to let anything incoming from the network be applied and see if there is a push needed
248252
// Note: this also starts periodic jobs, so we don't need to keep doing it
249-
void UserSync.queueNewJobIfNeeded();
253+
await UserSync.queueNewJobIfNeeded();
254+
255+
// on app startup, check that the avatar expiry on the file server
256+
const avatarPointer = ConvoHub.use()
257+
.get(UserUtils.getOurPubKeyStrFromCache())
258+
.getAvatarPointer();
259+
if (avatarPointer) {
260+
const details = await getFileInfoFromFileServer(avatarPointer);
261+
if (details?.expiryMs) {
262+
await Storage.put(SettingsKey.ntsAvatarExpiryMs, details.expiryMs);
263+
}
264+
}
250265
}, 20000);
251266

252267
global.setTimeout(() => {
@@ -328,13 +343,16 @@ export const ActionsPanel = () => {
328343
void SnodePool.forceRefreshRandomSnodePool();
329344
}, DURATION.MINUTES * 5);
330345

331-
useInterval(() => {
332-
if (!ourPrimaryConversation) {
333-
return;
334-
}
335-
// this won't be run every days, but if the app stays open for more than 10 days
336-
void triggerAvatarReUploadIfNeeded();
337-
}, DURATION.DAYS * 1);
346+
useInterval(
347+
() => {
348+
if (!ourPrimaryConversation) {
349+
return;
350+
}
351+
// this won't be run every days, but if the app stays open for more than 10 days
352+
void triggerAvatarReUploadIfNeeded();
353+
},
354+
window.sessionFeatureFlags.fsTTL30s ? DURATION.SECONDS * 1 : DURATION.DAYS * 1
355+
);
338356

339357
useCheckReleasedFeatures();
340358

ts/data/settings-key.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const settingsOpengroupPruning = 'prune-setting';
1111
const settingsNotification = 'notification-setting';
1212
const settingsAudioNotification = 'audio-notification-setting';
1313
const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
14-
const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
14+
const ntsAvatarExpiryMs = 'ntsAvatarExpiryMs';
1515
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
1616
const hasFollowSystemThemeEnabled = 'hasFollowSystemThemeEnabled';
1717
const hideRecoveryPassword = 'hideRecoveryPassword';
@@ -36,7 +36,7 @@ export const SettingsKey = {
3636
settingsNotification,
3737
settingsAudioNotification,
3838
hasSyncedInitialConfigurationItem,
39-
lastAvatarUploadTimestamp,
39+
ntsAvatarExpiryMs,
4040
hasLinkPreviewPopupBeenDisplayed,
4141
latestUserProfileEnvelopeTimestamp,
4242
latestUserGroupEnvelopeTimestamp,

ts/interactions/avatar-interactions/nts-avatar-interactions.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ export async function reuploadCurrentAvatarUs() {
5656

5757
const decryptedAvatarData = await blob.arrayBuffer();
5858

59-
return uploadAndSetOurAvatarShared({ decryptedAvatarData, ourConvo, profileKey });
59+
return uploadAndSetOurAvatarShared({
60+
decryptedAvatarData,
61+
ourConvo,
62+
profileKey,
63+
});
6064
}
6165

6266
export async function uploadAndSetOurAvatarShared({
@@ -80,8 +84,10 @@ export async function uploadAndSetOurAvatarShared({
8084
window.log.warn('failed to upload avatar to file server');
8185
return null;
8286
}
83-
const { fileUrl } = avatarPointer;
87+
const { fileUrl, expiresMs } = avatarPointer;
8488

89+
// Note: processing the avatar here doesn't change the buffer (unless the first one was uploaded as an image too big for an avatar.)
90+
// so, once we have deterministic encryption of avatars, the uploaded should always have the same hash
8591
const { avatarFallback, mainAvatarDetails } = await processAvatarData(decryptedAvatarData);
8692

8793
// this encrypts and save the new avatar and returns a new attachment path
@@ -111,7 +117,7 @@ export async function uploadAndSetOurAvatarShared({
111117
displayName,
112118
avatarPointer: fileUrl,
113119
});
114-
await Storage.put(SettingsKey.lastAvatarUploadTimestamp, Date.now());
120+
await Storage.put(SettingsKey.ntsAvatarExpiryMs, expiresMs);
115121

116122
return {
117123
avatarPointer: ourConvo.getAvatarPointer(),

ts/session/apis/file_server_api/FileServerApi.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import AbortController from 'abort-controller';
2+
import { isEmpty, isFinite, isNumber, isString } from 'lodash';
3+
24
import { BlindingActions } from '../../../webworker/workers/browser/libsession_worker_interface';
35
import { OnionSending } from '../../onions/onionSend';
46
import {
@@ -20,14 +22,27 @@ const RELEASE_VERSION_ENDPOINT = '/session_version';
2022

2123
const POST_GET_FILE_ENDPOINT = '/file';
2224

25+
function fileUrlToFileId(fullURL?: string) {
26+
const prefix = `${fileServerURL}${POST_GET_FILE_ENDPOINT}/`;
27+
if (!fullURL || !fullURL.startsWith(prefix)) {
28+
return null;
29+
}
30+
const fileId = fullURL.substring(prefix.length);
31+
32+
if (!fileId) {
33+
return null;
34+
}
35+
return fileId;
36+
}
37+
2338
/**
2439
* Upload a file to the file server v2 using the onion v4 encoding
2540
* @param fileContent the data to send
2641
* @returns null or the complete URL to share this file
2742
*/
2843
export const uploadFileToFsWithOnionV4 = async (
2944
fileContent: ArrayBuffer
30-
): Promise<{ fileUrl: string } | null> => {
45+
): Promise<{ fileUrl: string; expiresMs: number } | null> => {
3146
if (!fileContent || !fileContent.byteLength) {
3247
return null;
3348
}
@@ -46,12 +61,23 @@ export const uploadFileToFsWithOnionV4 = async (
4661
}
4762

4863
const fileId = result?.body?.id as number | undefined;
49-
if (!fileId) {
64+
const expires = result?.body?.expires as number; // expires is returned as a floating point timestamp in seconds, i.e. 1754863793.186137.
65+
if (
66+
!fileId ||
67+
!isString(fileId) ||
68+
isEmpty(fileId) ||
69+
!expires ||
70+
!isNumber(expires) ||
71+
!isFinite(expires)
72+
) {
5073
return null;
5174
}
5275
const fileUrl = `${fileServerURL}${POST_GET_FILE_ENDPOINT}/${fileId}`;
76+
const expiresMs = Math.floor(expires * 1000);
77+
console.warn('fileUrl', fileUrl);
5378
return {
5479
fileUrl,
80+
expiresMs,
5581
};
5682
};
5783

@@ -179,3 +205,36 @@ export const getLatestReleaseFromFileServer = async (
179205
}
180206
return [latestVersionWithV, releaseType || releaseChannel];
181207
};
208+
209+
/**
210+
* Fetch the expiry in ms of the corresponding file.
211+
*/
212+
export const getFileInfoFromFileServer = async (
213+
fileUrl: string
214+
): Promise<{ expiryMs: number } | null> => {
215+
const fileId = fileUrlToFileId(fileUrl);
216+
217+
if (!fileId) {
218+
throw new Error("getFileInfoFromFileServer: fileUrl doesn't start with the file server url");
219+
}
220+
221+
const result = await OnionSending.sendJsonViaOnionV4ToFileServer({
222+
abortSignal: new AbortController().signal,
223+
stringifiedBody: null,
224+
endpoint: `${POST_GET_FILE_ENDPOINT}/${fileId}/info`,
225+
method: 'GET',
226+
timeoutMs: 10 * DURATION.SECONDS, // longer time for file upload
227+
headers: {},
228+
});
229+
230+
const fileExpirySeconds = result?.body?.expires as number | undefined;
231+
232+
if (!batchGlobalIsSuccess(result)) {
233+
return null;
234+
}
235+
236+
if (!fileExpirySeconds || !isNumber(fileExpirySeconds) || !isFinite(fileExpirySeconds)) {
237+
return null;
238+
}
239+
return { expiryMs: Math.floor(fileExpirySeconds * 1000) };
240+
};

ts/state/ducks/metaGroups.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,7 @@ async function handleAvatarChangeFromUI({
10231023
// encrypt the avatar data with the profile key
10241024
const encryptedData = await encryptProfile(processed.mainAvatarDetails.outputBuffer, profileKey);
10251025

1026+
// TODO: we should store the expiries of the attachment somewhere in libsession I assume, and reupload as needed
10261027
const uploadedFileDetails = await uploadFileToFsWithOnionV4(encryptedData);
10271028
if (!uploadedFileDetails || !uploadedFileDetails.fileUrl) {
10281029
window?.log?.warn('File upload for groupv2 to file server failed');

0 commit comments

Comments
 (0)