VulpesOS/gaia/apps/email/js/ext/pop3/account.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

310 lines
9.6 KiB
JavaScript
Executable file

define([
'logic',
'../errbackoff',
'../composite/incoming',
'./sync',
'../errorutils',
'./jobs',
'../drafts/draft_rep',
'../disaster-recovery',
'module',
'require',
'exports'],
function(
logic,
errbackoff,
incoming,
pop3sync,
errorutils,
pop3jobs,
draftRep,
DisasterRecovery,
module,
require,
exports
) {
var CompositeIncomingAccount = incoming.CompositeIncomingAccount;
/**
* Define a POP3 account. Much of the functionality here is similar
* to IMAP; Pop3Account inherits the shared parts from
* CompositeIncomingAccount.
*/
function Pop3Account(universe, compositeAccount, accountId, credentials,
connInfo, folderInfos, dbConn, existingProtoConn) {
logic.defineScope(this, 'Account', { accountId: accountId,
accountType: 'pop3' });
CompositeIncomingAccount.apply(
this, [pop3sync.Pop3FolderSyncer].concat(Array.slice(arguments)));
// Set up connection information. We can't make much use of
// connection pooling since the POP3 protocol only allows one client
// to access a mailbox at a given time, so there's no connection pool.
this._conn = null;
this._pendingConnectionRequests = [];
this._backoffEndpoint = errbackoff.createEndpoint('pop3:' + this.id, this);
// If we have an existing connection from setting up the account, we
// can reuse that during the first sync.
if (existingProtoConn) {
DisasterRecovery.associateSocketWithAccount(
existingProtoConn.socket, this);
this._conn = existingProtoConn;
}
// Immediately ensure that we have any required local-only folders,
// as those can be created even while offline.
this.ensureEssentialOfflineFolders();
this._jobDriver = new pop3jobs.Pop3JobDriver(
this, this._folderInfos.$mutationState);
}
exports.Account = exports.Pop3Account = Pop3Account;
Pop3Account.prototype = Object.create(CompositeIncomingAccount.prototype);
var properties = {
type: 'pop3',
supportsServerFolders: false,
toString: function() {
return '[Pop3Account: ' + this.id + ']';
},
/**
* Call the callback with a live, authenticated connection. Clients
* should call done() when finished with the connection. (In our
* case, pop3/sync.js has a lazyWithConnection wrapper which
* abstracts the `done` callback.)
* @param {function(err, conn, done)} cb
*/
withConnection: function(cb, whyLabel) {
// This implementation serializes withConnection requests so that
// we don't step on requests' toes. While Pop3Client wouldn't mix
// up the requests themselves, interleaving different operations
// could result in undesired consequences.
this._pendingConnectionRequests.push(cb);
var done = function() {
var req = this._pendingConnectionRequests.shift();
if (req) {
var next = function(err) {
if (err) {
req(err);
done();
} else {
req(null, this._conn, done);
}
}.bind(this);
if (!this._conn || this._conn.state === 'disconnected') {
this._makeConnection(next, whyLabel);
} else {
next();
}
}
}.bind(this);
if (this._pendingConnectionRequests.length === 1) {
done();
}
},
/** @override */
__folderDoneWithConnection: function(conn) {
// IMAP uses this function to perform folder-specific connection cleanup.
// We don't need to do anything here.
},
/**
* Create a new POP3 connection, and call the callback when we
* have established the connection (or with an error if we failed).
* Since POP3 only uses one connection at a time, this function also
* assigns the given connection to this._conn.
*
* Don't use this function directly; instead use `withConnection` or
* a higher-level wrapper.
*
* @param {function(err, conn)} callback
* @param {String} whyLabel A descriptive name for the connection.
*/
_makeConnection: function(callback, whyLabel) {
// Mark a pending connection synchronously; the require call will
// not return until at least the next turn of the event loop, so
// we need to know that there's a pending connection request in
// progress.
this._conn = true;
// Dynamically load the probe/pop3 code to speed up startup.
require(['./pop3', './probe'], function(pop3, pop3probe) {
logic(this, 'createConnection', { label: whyLabel });
var opts = {
host: this._connInfo.hostname,
port: this._connInfo.port,
crypto: this._connInfo.crypto,
preferredAuthMethod: this._connInfo.preferredAuthMethod,
username: this._credentials.username,
password: this._credentials.password,
};
var conn = this._conn = new pop3.Pop3Client(opts, function(err) {
if (err) {
// Failed to get the connection:
console.error('Connect error:', err.name, 'formal:', err, 'on',
this._connInfo.hostname, this._connInfo.port);
err = pop3probe.normalizePop3Error(err);
if (errorutils.shouldReportProblem(err)) {
this.universe.__reportAccountProblem(
this.compositeAccount, err, 'incoming');
}
callback && callback(err, null);
conn.close();
// Track this failure for backoff purposes.
if (errorutils.shouldRetry(err)) {
if (this._backoffEndpoint.noteConnectFailureMaybeRetry(
errorutils.wasErrorFromReachableState(err))) {
this._backoffEndpoint.scheduleConnectAttempt(
this._makeConnection.bind(this));
} else {
this._backoffEndpoint.noteBrokenConnection();
}
} else {
this._backoffEndpoint.noteBrokenConnection();
}
}
// Succeeded:
else {
this._backoffEndpoint.noteConnectSuccess();
callback && callback(null, conn);
}
}.bind(this));
DisasterRecovery.associateSocketWithAccount(conn.socket, this);
}.bind(this));
},
/**
* Save an attachment-stripped version of the sent draft to our sent folder.
*/
saveSentMessage: function(composer) {
var sentFolder = this.getFirstFolderWithType('sent');
if (!sentFolder) {
return;
}
var sentStorage = this.getFolderStorageForFolderId(sentFolder.id);
var id = sentStorage._issueNewHeaderId();
var suid = sentStorage.folderId + '/' + id;
var sentPieces = draftRep.cloneDraftMessageForSentFolderWithoutAttachments(
composer.header, composer.body, { id: id, suid: suid });
this.universe.saveSentDraft(sentFolder.id,
sentPieces.header, sentPieces.body);
},
/**
* Delete the given folder. (This always happens locally.)
*/
deleteFolder: function(folderId, callback) {
if (!this._folderInfos.hasOwnProperty(folderId)) {
throw new Error("No such folder: " + folderId);
}
var folderMeta = this._folderInfos[folderId].$meta;
logic(self, 'deleteFolder', { path: folderMeta.path });
self._forgetFolder(folderId);
callback && callback(null, folderMeta);
},
/**
* Shut down the account and close the connection.
*/
shutdown: function(callback) {
CompositeIncomingAccount.prototype.shutdownFolders.call(this);
this._backoffEndpoint.shutdown();
if (this._conn && this._conn.close) {
this._conn.close();
}
callback && callback();
},
/**
* Attempt to create a new, authenticated connection using the
* current credentials. If a current connection is already
* established, terminates the existing connection first.
*
* @param {function(err)} callback
*/
checkAccount: function(callback) {
// Disconnect first so as to properly check credentials.
if (this._conn !== null) {
if (this._conn.state !== 'disconnected') {
this._conn.close();
}
this._conn = null;
}
logic(this, 'checkAccount_begin');
this.withConnection(function(err) {
logic(this, 'checkAccount_end', { error: err });
callback(err);
}.bind(this), 'checkAccount');
},
/**
* Ensure that local-only folders exist. This is run immediately
* upon account initialization. Since POP3 doesn't support server
* folders, all folders are local-only, so this function does all
* the hard work.
*/
ensureEssentialOfflineFolders: function() {
// Create required folders if necessary.
[ 'sent', 'localdrafts', 'trash', 'outbox' ].forEach(function(folderType) {
if (!this.getFirstFolderWithType(folderType)) {
this._learnAboutFolder(
/* name: */ folderType,
/* path: */ folderType,
/* parentId: */ null,
/* type: */ folderType,
/* delim: */ '',
/* depth: */ 0,
/* suppressNotification: */ true);
}
}, this);
},
/**
* POP3 doesn't support server folders, so all folder creation is
* done in `ensureEssentialOfflineFolders`.
* @param {function} callback
* Called immediately, for homogeneity with other account types.
*/
ensureEssentialOnlineFolders: function(callback) {
// All the important work is already done. Yay POP3!
callback && callback();
},
/**
* Destroy the account when the account has been deleted.
*/
accountDeleted: function() {
this._alive = false;
this.shutdown();
},
};
// Inherit Pop3Account from CompositeIncomingAccount:
// XXX: Use mix.js when it lands in the streaming patch.
for (var k in properties) {
Object.defineProperty(Pop3Account.prototype, k,
Object.getOwnPropertyDescriptor(properties, k));
}
}); // end define