VulpesOS/gaia/apps/email/js/ext/composite/configurator.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

343 lines
12 KiB
JavaScript
Executable file

/**
* Configurator for imap+smtp and pop3+smtp.
**/
define(
[
'logic',
'../accountcommon',
'../a64',
'../allback',
'./account',
'../date',
'require',
'exports'
],
function(
logic,
$accountcommon,
$a64,
$allback,
$account,
$date,
require,
exports
) {
var allbackMaker = $allback.allbackMaker;
exports.account = $account;
exports.configurator = {
tryToCreateAccount: function(universe, userDetails, domainInfo,
callback) {
var credentials, incomingInfo, smtpConnInfo, incomingType;
if (domainInfo) {
incomingType = (domainInfo.type === 'imap+smtp' ? 'imap' : 'pop3');
var password = null;
// If the account has an outgoingPassword, use that; otherwise
// use the main password. We must take care to treat null values
// as potentially valid in the future, if we allow password-free
// account configurations.
if (userDetails.outgoingPassword !== undefined) {
password = userDetails.outgoingPassword;
} else {
password = userDetails.password;
}
credentials = {
username: domainInfo.incoming.username,
password: userDetails.password,
outgoingUsername: domainInfo.outgoing.username,
outgoingPassword: password,
};
if (domainInfo.oauth2Tokens) {
// We need to save off all the information so:
// - the front-end can reauthorize exclusively from this info.
// - the back-end can refresh its token
// - on upgrades so we can know if our scope isn't good enough. (Note
// that we're not saving off the secret group; upgrades would need to
// factor in the auth or token endpoints.)
credentials.oauth2 = {
authEndpoint: domainInfo.oauth2Settings.authEndpoint,
tokenEndpoint: domainInfo.oauth2Settings.tokenEndpoint,
scope: domainInfo.oauth2Settings.scope,
clientId: domainInfo.oauth2Secrets.clientId,
clientSecret: domainInfo.oauth2Secrets.clientSecret,
refreshToken: domainInfo.oauth2Tokens.refreshToken,
accessToken: domainInfo.oauth2Tokens.accessToken,
expireTimeMS: domainInfo.oauth2Tokens.expireTimeMS,
// Treat the access token like it was recently retrieved; although we
// generally expect the XOAUTH2 case should go through without
// failure, in the event something is wrong, immediately re-fetching
// a new accessToken is not going to be useful for us.
_transientLastRenew: $date.PERFNOW()
};
}
incomingInfo = {
hostname: domainInfo.incoming.hostname,
port: domainInfo.incoming.port,
crypto: (typeof domainInfo.incoming.socketType === 'string' ?
domainInfo.incoming.socketType.toLowerCase() :
domainInfo.incoming.socketType),
};
if (incomingType === 'pop3') {
incomingInfo.preferredAuthMethod = null;
}
smtpConnInfo = {
emailAddress: userDetails.emailAddress, // used for probing
hostname: domainInfo.outgoing.hostname,
port: domainInfo.outgoing.port,
crypto: (typeof domainInfo.outgoing.socketType === 'string' ?
domainInfo.outgoing.socketType.toLowerCase() :
domainInfo.outgoing.socketType),
};
}
// Note: For OAUTH accounts, the credentials may be updated
// in-place if a new access token was required. We don't need to
// explicitly save those changes here because we define the
// account with the same object below.
var incomingPromise = new Promise(function(resolve, reject) {
if (incomingType === 'imap') {
require(['../imap/probe'], function(probe) {
probe.probeAccount(credentials, incomingInfo).then(resolve, reject);
});
} else {
require(['../pop3/probe'], function(probe) {
probe.probeAccount(credentials, incomingInfo).then(resolve, reject);
});
}
});
var outgoingPromise = new Promise(function(resolve, reject) {
require(['../smtp/probe'], function(probe) {
probe.probeAccount(credentials, smtpConnInfo).then(resolve, reject);
});
});
// Note: Promise.all() will fire the catch handler as soon as one
// of the promises is rejected. While this means we will only see
// the first error that returns, it actually works well for our
// semantics, as we only notify the user about one side's problems
// at a time.
Promise.all([incomingPromise, outgoingPromise])
.then(function(results) {
var incomingConn = results[0].conn;
var defineAccount;
if (incomingType === 'imap') {
defineAccount = this._defineImapAccount;
} else if (incomingType === 'pop3') {
incomingInfo.preferredAuthMethod = incomingConn.authMethod;
defineAccount = this._definePop3Account;
}
defineAccount.call(this,
universe, userDetails, credentials,
incomingInfo, smtpConnInfo, incomingConn,
callback);
}.bind(this))
.catch(function(ambiguousErr) {
// One of the account sides failed. Normally we leave the
// IMAP/POP3 side open for reuse, but if the SMTP
// configuration falied we must close the incoming connection.
// (If the incoming side failed as well, we won't receive the
// `.then` callback.)
incomingPromise.then(function incomingOkButOutgoingFailed(result) {
result.conn.close();
// the error is no longer ambiguous; it was SMTP
callback(ambiguousErr, /* conn: */ null,
{ server: smtpConnInfo.hostname });
}).catch(function incomingFailed(incomingErr) {
callback(incomingErr, /* conn: */ null,
{ server: incomingInfo.hostname });
});
});
},
recreateAccount: function(universe, oldVersion, oldAccountInfo, callback) {
var oldAccountDef = oldAccountInfo.def;
var credentials = {
username: oldAccountDef.credentials.username,
password: oldAccountDef.credentials.password,
// (if these two keys are null, keep them that way:)
outgoingUsername: oldAccountDef.credentials.outgoingUsername,
outgoingPassword: oldAccountDef.credentials.outgoingPassword,
authMechanism: oldAccountDef.credentials.authMechanism,
oauth2: oldAccountDef.credentials.oauth2
};
var accountId = $a64.encodeInt(universe.config.nextAccountNum++);
var oldType = oldAccountDef.type || 'imap+smtp';
var accountDef = {
id: accountId,
name: oldAccountDef.name,
type: oldType,
receiveType: oldType.split('+')[0],
sendType: 'smtp',
syncRange: oldAccountDef.syncRange,
syncInterval: oldAccountDef.syncInterval || 0,
notifyOnNew: oldAccountDef.hasOwnProperty('notifyOnNew') ?
oldAccountDef.notifyOnNew : true,
playSoundOnSend: oldAccountDef.hasOwnProperty('playSoundOnSend') ?
oldAccountDef.playSoundOnSend : true,
credentials: credentials,
receiveConnInfo: {
hostname: oldAccountDef.receiveConnInfo.hostname,
port: oldAccountDef.receiveConnInfo.port,
crypto: oldAccountDef.receiveConnInfo.crypto,
preferredAuthMethod:
oldAccountDef.receiveConnInfo.preferredAuthMethod || null,
},
sendConnInfo: {
hostname: oldAccountDef.sendConnInfo.hostname,
port: oldAccountDef.sendConnInfo.port,
crypto: oldAccountDef.sendConnInfo.crypto,
},
identities: $accountcommon.recreateIdentities(universe, accountId,
oldAccountDef.identities)
};
this._loadAccount(universe, accountDef,
oldAccountInfo.folderInfo, null, function(account) {
callback(null, account, null);
});
},
/**
* Define an account now that we have verified the credentials are good and
* the server meets our minimal functionality standards. We are also
* provided with the protocol connection that was used to perform the check
* so we can immediately put it to work.
*/
_defineImapAccount: function(universe, userDetails, credentials,
incomingInfo, smtpConnInfo, imapProtoConn,
callback) {
var accountId = $a64.encodeInt(universe.config.nextAccountNum++);
var accountDef = {
id: accountId,
name: userDetails.accountName || userDetails.emailAddress,
defaultPriority: $date.NOW(),
type: 'imap+smtp',
receiveType: 'imap',
sendType: 'smtp',
syncRange: 'auto',
syncInterval: userDetails.syncInterval || 0,
notifyOnNew: userDetails.hasOwnProperty('notifyOnNew') ?
userDetails.notifyOnNew : true,
playSoundOnSend: userDetails.hasOwnProperty('playSoundOnSend') ?
userDetails.playSoundOnSend : true,
credentials: credentials,
receiveConnInfo: incomingInfo,
sendConnInfo: smtpConnInfo,
identities: [
{
id: accountId + '/' +
$a64.encodeInt(universe.config.nextIdentityNum++),
name: userDetails.displayName,
address: userDetails.emailAddress,
replyTo: null,
signature: null,
signatureEnabled: false
},
]
};
this._loadAccount(universe, accountDef, null,
imapProtoConn, function(account) {
callback(null, account, null);
});
},
/**
* Define an account now that we have verified the credentials are good and
* the server meets our minimal functionality standards. We are also
* provided with the protocol connection that was used to perform the check
* so we can immediately put it to work.
*/
_definePop3Account: function(universe, userDetails, credentials,
incomingInfo, smtpConnInfo, pop3ProtoConn,
callback) {
var accountId = $a64.encodeInt(universe.config.nextAccountNum++);
var accountDef = {
id: accountId,
name: userDetails.accountName || userDetails.emailAddress,
defaultPriority: $date.NOW(),
type: 'pop3+smtp',
receiveType: 'pop3',
sendType: 'smtp',
syncRange: 'auto',
syncInterval: userDetails.syncInterval || 0,
notifyOnNew: userDetails.hasOwnProperty('notifyOnNew') ?
userDetails.notifyOnNew : true,
playSoundOnSend: userDetails.hasOwnProperty('playSoundOnSend') ?
userDetails.playSoundOnSend : true,
credentials: credentials,
receiveConnInfo: incomingInfo,
sendConnInfo: smtpConnInfo,
identities: [
{
id: accountId + '/' +
$a64.encodeInt(universe.config.nextIdentityNum++),
name: userDetails.displayName,
address: userDetails.emailAddress,
replyTo: null,
signature: null,
signatureEnabled: false
},
],
};
this._loadAccount(universe, accountDef, null,
pop3ProtoConn, function(account) {
callback(null, account, null);
});
},
/**
* Save the account def and folder info for our new (or recreated) account and
* then load it.
*/
_loadAccount: function(universe, accountDef, oldFolderInfo, protoConn,
callback) {
var folderInfo;
if (accountDef.receiveType === 'imap') {
folderInfo = {
$meta: {
nextFolderNum: 0,
nextMutationNum: 0,
lastFolderSyncAt: 0,
capability: (oldFolderInfo && oldFolderInfo.$meta.capability) ||
protoConn.capability
},
$mutations: [],
$mutationState: {},
};
} else { // POP3
folderInfo = {
$meta: {
nextFolderNum: 0,
nextMutationNum: 0,
lastFolderSyncAt: 0,
},
$mutations: [],
$mutationState: {},
};
}
universe.saveAccountDef(accountDef, folderInfo);
universe._loadAccount(accountDef, folderInfo, protoConn, callback);
},
};
}); // end define