Skip to content

Commit 26290ff

Browse files
committed
fix: mark attachment as failure if we get a 404
1 parent 53c57ef commit 26290ff

File tree

5 files changed

+55
-37
lines changed

5 files changed

+55
-37
lines changed

ts/session/apis/file_server_api/FileServerApi.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,13 @@ export const downloadFileFromFileServer = async (
7777
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
7878
window.log.info(`about to try to download fsv2: "${urlToGet}"`);
7979
}
80+
81+
// this throws if we get a 404 from the file server
8082
const result = await OnionSending.getBinaryViaOnionV4FromFileServer({
8183
abortSignal: new AbortController().signal,
8284
endpoint: urlToGet,
8385
method: 'GET',
86+
throwError: true,
8487
});
8588
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
8689
window.log.info(`download fsv2: "${urlToGet} got result:`, JSON.stringify(result));

ts/session/apis/open_group_api/sogsv3/sogsV3FetchFile.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
2020
headers: Record<string, any> | null;
2121
roomId: string;
2222
fileId: string;
23+
throwError: boolean;
2324
}): Promise<Uint8Array | null> {
2425
const {
2526
serverUrl,
@@ -30,6 +31,7 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
3031
doNotIncludeOurSogsHeaders,
3132
roomId,
3233
fileId,
34+
throwError,
3335
} = sendOptions;
3436

3537
const stringifiedBody = null;
@@ -62,12 +64,12 @@ export async function fetchBinaryFromSogsWithOnionV4(sendOptions: {
6264
body: stringifiedBody,
6365
useV4: true,
6466
},
65-
false,
67+
throwError,
6668
abortSignal
6769
);
6870

6971
if (!res?.bodyBinary) {
70-
window.log.info('fetchBinaryFromSogsWithOnionV4 no binary content');
72+
window.log.info('fetchBinaryFromSogsWithOnionV4 no binary content with code', res?.status_code);
7173
return null;
7274
}
7375
return res.bodyBinary;
@@ -169,6 +171,7 @@ const sogsV3FetchPreview = async (
169171
doNotIncludeOurSogsHeaders: true,
170172
roomId: roomInfos.roomId,
171173
fileId: roomInfos.imageID,
174+
throwError: false,
172175
});
173176
if (fetched && fetched.byteLength) {
174177
return fetched;
@@ -198,6 +201,7 @@ export const sogsV3FetchFileByFileID = async (
198201
doNotIncludeOurSogsHeaders: true,
199202
roomId: roomInfos.roomId,
200203
fileId,
204+
throwError: true,
201205
});
202206
return fetched && fetched.byteLength ? fetched : null;
203207
};

ts/session/apis/snode_api/onions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ export const resetSnodeFailureCount = () => {
2727
const snodeFailureThreshold = 3;
2828

2929
export const OXEN_SERVER_ERROR = 'Oxen Server error';
30+
31+
// Not ideal, but a pRetry.AbortError only lets us customize the message, and not the code
32+
const errorContent404 = ': 404 ';
33+
export const was404Error = (error: Error) => error.message.includes(errorContent404);
34+
35+
export const buildErrorMessageWithFailedCode = (prefix: string, code: number, suffix: string) =>
36+
`${prefix}: ${code} ${suffix}`;
37+
3038
/**
3139
* When sending a request over onion, we might get two status.
3240
* The first one, on the request itself, the other one in the json returned.

ts/session/onions/onionSend.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { OnionPaths } from '.';
44
import {
5+
buildErrorMessageWithFailedCode,
56
FinalDestNonSnodeOptions,
67
FinalRelayOptions,
78
Onions,
@@ -212,10 +213,17 @@ const sendViaOnionV4ToNonSnodeWithRetries = async (
212213
};
213214
}
214215
if (foundStatusCode === 404) {
215-
// this is most likely that a 404 won't fix itself. So just stop right here retries by throwing a non retryable error
216-
throw new pRetry.AbortError(
216+
window.log.warn(
217217
`Got 404 while sendViaOnionV4ToNonSnodeWithRetries with url:${url}. Stopping retries`
218218
);
219+
// most likely, a 404 won't fix itself. So just stop right here retries by throwing a non retryable error
220+
throw new pRetry.AbortError(
221+
buildErrorMessageWithFailedCode(
222+
'sendViaOnionV4ToNonSnodeWithRetries',
223+
404,
224+
`with url:${url}. Stopping retries`
225+
)
226+
);
219227
}
220228
// we consider those cases as an error, and trigger a retry (if possible), by throwing a non-abortable error
221229
throw new Error(
@@ -239,7 +247,7 @@ const sendViaOnionV4ToNonSnodeWithRetries = async (
239247
}
240248
);
241249
} catch (e) {
242-
window?.log?.warn('sendViaOnionV4ToNonSnodeRetryable failed ', e.message);
250+
window?.log?.warn('sendViaOnionV4ToNonSnodeRetryable failed ', e.message, throwErrors);
243251
if (throwErrors) {
244252
throw e;
245253
}
@@ -455,8 +463,9 @@ async function getBinaryViaOnionV4FromFileServer(sendOptions: {
455463
endpoint: string;
456464
method: string;
457465
abortSignal: AbortSignal;
466+
throwError: boolean;
458467
}): Promise<OnionV4BinarySnodeResponse | null> {
459-
const { endpoint, method, abortSignal } = sendOptions;
468+
const { endpoint, method, abortSignal, throwError } = sendOptions;
460469
if (!endpoint.startsWith('/')) {
461470
throw new Error('endpoint needs a leading /');
462471
}
@@ -465,6 +474,9 @@ async function getBinaryViaOnionV4FromFileServer(sendOptions: {
465474
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
466475
window.log.info(`getBinaryViaOnionV4FromFileServer fsv2: "${builtUrl} `);
467476
}
477+
478+
// this throws for a bunch of reasons.
479+
// One of them, is if we get a 404 (i.e. the file server was reached but reported no such attachments exists)
468480
const res = await OnionSending.sendViaOnionV4ToNonSnodeWithRetries(
469481
fileServerPubKey,
470482
builtUrl,
@@ -474,7 +486,7 @@ async function getBinaryViaOnionV4FromFileServer(sendOptions: {
474486
body: null,
475487
useV4: true,
476488
},
477-
false,
489+
throwError,
478490
abortSignal
479491
);
480492

ts/session/utils/AttachmentsDownload.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { MessageModel } from '../../models/message';
88
import { downloadAttachment, downloadAttachmentSogsV3 } from '../../receiver/attachments';
99
import { initializeAttachmentLogic, processNewAttachment } from '../../types/MessageAttachment';
1010
import { getAttachmentMetadata } from '../../types/message/initializeAttachmentMetadata';
11+
import { was404Error } from '../apis/snode_api/onions';
1112

1213
// this may cause issues if we increment that value to > 1, but only having one job will block the whole queue while one attachment is downloading
1314
const MAX_ATTACHMENT_JOB_PARALLELISM = 3;
@@ -175,6 +176,7 @@ async function _runJob(job: any) {
175176
let downloaded;
176177

177178
try {
179+
// those two functions throw if they get a 404
178180
if (isOpenGroupV2) {
179181
downloaded = await downloadAttachmentSogsV3(attachment, openGroupV2Details);
180182
} else {
@@ -189,14 +191,12 @@ async function _runJob(job: any) {
189191
} from message ${found.idForLogging()} as permanent error`
190192
);
191193

192-
await _finishJob(found, id);
193-
194194
// Make sure to fetch the message from DB here right before writing it.
195195
// This is to avoid race condition where multiple attachments in a single message get downloaded at the same time,
196196
// and tries to update the same message.
197197
found = await Data.getMessageById(messageId);
198-
199198
_addAttachmentToMessage(found, _markAttachmentAsError(attachment), { type, index });
199+
await _finishJob(found, id);
200200

201201
return;
202202
}
@@ -232,7 +232,9 @@ async function _runJob(job: any) {
232232
// tslint:disable: restrict-plus-operands
233233
const currentAttempt: 1 | 2 | 3 = (attempts || 0) + 1;
234234

235-
if (currentAttempt >= 3) {
235+
// if we get a 404 error for attachment downloaded, we can safely assume that the attachment expired server-side.
236+
// so there is no need to continue trying to download it.
237+
if (currentAttempt >= 3 || was404Error(error)) {
236238
logger.error(
237239
`_runJob: ${currentAttempt} failed attempts, marking attachment ${id} from message ${found?.idForLogging()} as permament error:`,
238240
error && error.stack ? error.stack : error
@@ -321,40 +323,29 @@ function _addAttachmentToMessage(
321323
return;
322324
}
323325

324-
if (type === 'preview') {
326+
// for quote and previews, if the attachment cannot be downloaded we just erase it from the message itself, so just the title or body is rendered
327+
if (type === 'preview' || type === 'quote') {
328+
if (type === 'quote') {
329+
const quote = message.get('quote');
330+
if (!quote) {
331+
throw new Error("_addAttachmentToMessage: quote didn't exist");
332+
}
333+
334+
delete message.attributes.quote.attachments;
335+
336+
return;
337+
}
325338
const preview = message.get('preview');
326339
if (!preview || preview.length <= index) {
327340
throw new Error(`_addAttachmentToMessage: preview didn't exist or ${index} was too large`);
328341
}
329-
const item = preview[index];
330-
if (!item) {
331-
throw new Error(`_addAttachmentToMessage: preview ${index} was falsey`);
332-
}
333-
_replaceAttachment(item, 'image', attachment, logPrefix);
334-
return;
335-
}
336-
337-
if (type === 'quote') {
338-
const quote = message.get('quote');
339-
if (!quote) {
340-
throw new Error("_addAttachmentToMessage: quote didn't exist");
341-
}
342-
const { attachments } = quote;
343-
if (!attachments || attachments.length <= index) {
344-
throw new Error(
345-
`_addAttachmentToMessage: quote attachments didn't exist or ${index} was too large`
346-
);
347-
}
348-
349-
const item = attachments[index];
350-
if (!item) {
351-
throw new Error(`_addAttachmentToMessage: attachment ${index} was falsey`);
352-
}
353-
_replaceAttachment(item, 'thumbnail', attachment, logPrefix);
354342

343+
delete message.attributes.preview[0].image;
355344
return;
356345
}
357346

347+
// for quote and previews, if the attachment cannot be downloaded we just erase it from the message itself, so just the title or body is rendered
348+
358349
throw new Error(
359350
`_addAttachmentToMessage: Unknown job type ${type} for message ${message.idForLogging()}`
360351
);

0 commit comments

Comments
 (0)