Projektstart
This commit is contained in:
222
backend/node_modules/imapflow/lib/commands/fetch.js
generated
vendored
Normal file
222
backend/node_modules/imapflow/lib/commands/fetch.js
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
'use strict';
|
||||
|
||||
const { formatMessageResponse } = require('../tools');
|
||||
|
||||
// Fetches emails from server
|
||||
module.exports = async (connection, range, query, options) => {
|
||||
if (connection.state !== connection.states.SELECTED || !range) {
|
||||
// nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
let mailbox = connection.mailbox;
|
||||
|
||||
const commandKey = connection.capabilities.has('BINARY') && options.binary && !connection.disableBinary ? 'BINARY' : 'BODY';
|
||||
|
||||
let retryCount = 0;
|
||||
const maxRetries = 4;
|
||||
const baseDelay = 1000; // Start with 1 second delay
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
let messages = {
|
||||
count: 0,
|
||||
list: []
|
||||
};
|
||||
|
||||
let response;
|
||||
try {
|
||||
let attributes = [{ type: 'SEQUENCE', value: (range || '*').toString() }];
|
||||
|
||||
let queryStructure = [];
|
||||
|
||||
let setBodyPeek = (attributes, partial) => {
|
||||
let bodyPeek = {
|
||||
type: 'ATOM',
|
||||
value: `${commandKey}.PEEK`,
|
||||
section: [],
|
||||
partial
|
||||
};
|
||||
|
||||
if (Array.isArray(attributes)) {
|
||||
attributes.forEach(attribute => {
|
||||
bodyPeek.section.push(attribute);
|
||||
});
|
||||
} else if (attributes) {
|
||||
bodyPeek.section.push(attributes);
|
||||
}
|
||||
|
||||
queryStructure.push(bodyPeek);
|
||||
};
|
||||
|
||||
['all', 'fast', 'full', 'uid', 'flags', 'bodyStructure', 'envelope', 'internalDate'].forEach(key => {
|
||||
if (query[key]) {
|
||||
queryStructure.push({ type: 'ATOM', value: key.toUpperCase() });
|
||||
}
|
||||
});
|
||||
|
||||
if (query.size) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'RFC822.SIZE' });
|
||||
}
|
||||
|
||||
if (query.source) {
|
||||
let partial;
|
||||
if (typeof query.source === 'object' && (query.source.start || query.source.maxLength)) {
|
||||
partial = [Number(query.source.start) || 0];
|
||||
if (query.source.maxLength && !isNaN(query.source.maxLength)) {
|
||||
partial.push(Number(query.source.maxLength));
|
||||
}
|
||||
}
|
||||
queryStructure.push({ type: 'ATOM', value: `${commandKey}.PEEK`, section: [], partial });
|
||||
}
|
||||
|
||||
// if possible, always request for unique email id
|
||||
if (connection.capabilities.has('OBJECTID')) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'EMAILID' });
|
||||
} else if (connection.capabilities.has('X-GM-EXT-1')) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'X-GM-MSGID' });
|
||||
}
|
||||
|
||||
if (query.threadId) {
|
||||
if (connection.capabilities.has('OBJECTID')) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'THREADID' });
|
||||
} else if (connection.capabilities.has('X-GM-EXT-1')) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'X-GM-THRID' });
|
||||
}
|
||||
}
|
||||
|
||||
if (query.labels) {
|
||||
if (connection.capabilities.has('X-GM-EXT-1')) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'X-GM-LABELS' });
|
||||
}
|
||||
}
|
||||
|
||||
// always ask for modseq if possible
|
||||
if (connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'MODSEQ' });
|
||||
}
|
||||
|
||||
// always make sure to include UID in the request as well even though server might auto-add it itself
|
||||
if (!query.uid) {
|
||||
queryStructure.push({ type: 'ATOM', value: 'UID' });
|
||||
}
|
||||
|
||||
if (query.headers) {
|
||||
if (Array.isArray(query.headers)) {
|
||||
setBodyPeek([{ type: 'ATOM', value: 'HEADER.FIELDS' }, query.headers.map(header => ({ type: 'ATOM', value: header }))]);
|
||||
} else {
|
||||
setBodyPeek({ type: 'ATOM', value: 'HEADER' });
|
||||
}
|
||||
}
|
||||
|
||||
if (query.bodyParts && query.bodyParts.length) {
|
||||
query.bodyParts.forEach(part => {
|
||||
if (!part) {
|
||||
return;
|
||||
}
|
||||
let key;
|
||||
let partial;
|
||||
if (typeof part === 'object') {
|
||||
if (!part.key || typeof part.key !== 'string') {
|
||||
return;
|
||||
}
|
||||
key = part.key.toUpperCase();
|
||||
if (part.start || part.maxLength) {
|
||||
partial = [Number(part.start) || 0];
|
||||
if (part.maxLength && !isNaN(part.maxLength)) {
|
||||
partial.push(Number(part.maxLength));
|
||||
}
|
||||
}
|
||||
} else if (typeof part === 'string') {
|
||||
key = part.toUpperCase();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
setBodyPeek({ type: 'ATOM', value: key }, partial);
|
||||
});
|
||||
}
|
||||
|
||||
if (queryStructure.length === 1) {
|
||||
queryStructure = queryStructure.pop();
|
||||
}
|
||||
|
||||
attributes.push(queryStructure);
|
||||
|
||||
if (options.changedSince && connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
||||
let changedSinceArgs = [
|
||||
{
|
||||
type: 'ATOM',
|
||||
value: 'CHANGEDSINCE'
|
||||
},
|
||||
{
|
||||
type: 'ATOM',
|
||||
value: options.changedSince.toString()
|
||||
}
|
||||
];
|
||||
|
||||
if (options.uid && connection.enabled.has('QRESYNC')) {
|
||||
changedSinceArgs.push({
|
||||
type: 'ATOM',
|
||||
value: 'VANISHED'
|
||||
});
|
||||
}
|
||||
|
||||
attributes.push(changedSinceArgs);
|
||||
}
|
||||
|
||||
response = await connection.exec(options.uid ? 'UID FETCH' : 'FETCH', attributes, {
|
||||
untagged: {
|
||||
FETCH: async untagged => {
|
||||
messages.count++;
|
||||
let formatted = await formatMessageResponse(untagged, mailbox);
|
||||
if (typeof options.onUntaggedFetch === 'function') {
|
||||
await new Promise((resolve, reject) => {
|
||||
options.onUntaggedFetch(formatted, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
messages.list.push(formatted);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
response.next();
|
||||
return messages;
|
||||
} catch (err) {
|
||||
if (err.code === 'ETHROTTLE') {
|
||||
// Calculate exponential backoff delay
|
||||
const backoffDelay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); // Cap at 30 seconds
|
||||
|
||||
// Use throttle reset time if provided and longer than backoff
|
||||
const delay = err.throttleReset && err.throttleReset > backoffDelay ? err.throttleReset : backoffDelay;
|
||||
|
||||
connection.log.warn({
|
||||
msg: 'Retrying throttled request with exponential backoff',
|
||||
cid: connection.id,
|
||||
code: err.code,
|
||||
response: err.responseText,
|
||||
throttleReset: err.throttleReset,
|
||||
retryCount,
|
||||
delayMs: delay
|
||||
});
|
||||
|
||||
// Wait before retrying
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
retryCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
connection.log.warn({ err, cid: connection.id });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user