Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
343 lines
12 KiB
JavaScript
Executable file
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
|