IDLE support

If the IMAP server supports it, the IDLE extension will automatically be used in place of NOOP/polling when a mailbox is open. This means notifications of mailbox changes (e.g. when new mail arrives) will automatically be pushed to the client as they happen. Rejoice!
fork
Brian White 14 years ago
parent 7a9ae9f9bd
commit c8a95325f0

@ -47,6 +47,14 @@ function ImapConnection (options) {
permFlags: [],
name: null,
messages: { total: 0, new: 0 }
},
ext: {
// Capability-specific state stuff
idle: {
MAX_WAIT: 1740000, // 29 mins in ms
sentIdle: false,
timeWaited: 0 // ms
}
}
};
this._options = extend(true, this._options, options);
@ -328,14 +336,12 @@ ImapConnection.prototype.connect = function(loginCb) {
curReq._fetcher.emit('message', msg);
msg.emit('end');
}
break;
}
}
}
} else if (data[0].indexOf('A') === 0) { // Tagged server response
var sendBox = false;
clearTimeout(self._state.tmrKeepalive);
self._state.tmrKeepalive = setTimeout(self._idleCheck.bind(self), self._state.tmoKeepalive);
if (self._state.status === STATES.BOXSELECTING) {
if (data[1] === 'OK') {
@ -347,6 +353,25 @@ ImapConnection.prototype.connect = function(loginCb) {
}
}
if (self._state.requests.length === 1) {
if (self._state.status === STATES.BOXSELECTED &&
self._state.capabilities.indexOf('IDLE') > -1) {
// According to RFC 2177, we should re-IDLE at least every 29
// minutes to avoid disconnection by the server
self._send('IDLE', undefined, true);
}
self._state.tmrKeepalive = setTimeout(function() {
if (self._state.isIdle) {
if (self._state.ext.idle.sentIdle) {
self._state.ext.idle.timeWaited += self._state.tmoKeepalive;
if (self._state.ext.idle.timeWaited >= self._state.ext.idle.MAX_WAIT)
self._send('IDLE', undefined, true); // restart IDLE
} else
self._noop();
}
}, self._state.tmoKeepalive);
}
if (self._state.requests[0].command.indexOf('RENAME') > -1) {
self._state.box.name = self._state.box._newName;
delete self._state.box._newName;
@ -363,7 +388,7 @@ ImapConnection.prototype.connect = function(loginCb) {
} else if (self._state.status === STATES.BOXSELECTED) {
if (sendBox) // SELECT, EXAMINE, RENAME
args.unshift(self._state.box);
// According to RFC3501, UID commands do not give errors for non-existant user-supplied UIDs,
// According to RFC 3501, UID commands do not give errors for non-existant user-supplied UIDs,
// so give the callback empty results if we unexpectedly received no untagged responses.
else if ((cmd.indexOf('UID FETCH') === 0 || cmd.indexOf('UID SEARCH') === 0) && args.length === 0)
args.unshift([]);
@ -722,23 +747,32 @@ ImapConnection.prototype._resetBox = function() {
this._state.box.messages.total = 0;
this._state.box.messages.new = 0;
};
ImapConnection.prototype._idleCheck = function() {
if (this._state.isIdle)
this._noop();
};
ImapConnection.prototype._noop = function() {
if (this._state.status >= STATES.AUTH)
this._send('NOOP', undefined);
this._send('NOOP');
};
ImapConnection.prototype._send = function(cmdstr, cb, bypass) {
if (arguments.length > 0 && !bypass)
if (typeof cmdstr !== 'undefined' && !bypass)
this._state.requests.push({ command: cmdstr, callback: cb, args: [] });
if ((arguments.length === 0 && this._state.requests.length > 0) || this._state.requests.length === 1 || bypass) {
if ((typeof cmdstr === 'undefined' && this._state.requests.length) ||
this._state.requests.length === 1 || bypass) {
var prefix = '', cmd = (bypass ? cmdstr : this._state.requests[0].command);
clearTimeout(this._state.tmrKeepalive);
this._state.isIdle = false;
var cmd = (bypass ? cmdstr : this._state.requests[0].command);
this._state.conn.cleartext.write('A' + ++this._state.curId + ' ' + cmd + CRLF);
debug('<<SENT>>: A' + this._state.curId + ' ' + cmd);
if (this._state.ext.idle.sentIdle && cmd !== 'DONE') {
this._send('DONE', undefined, true);
this._state.ext.idle.sentIdle = false;
this._state.ext.idle.timeWaited = 0;
} else if (cmd === 'IDLE') {
// we use a different prefix to differentiate and disregard the tagged
// response the server will send us when we issue DONE
prefix = 'IDLE ';
this._state.ext.idle.sentIdle = true;
}
if (cmd !== 'IDLE' && cmd !== 'DONE')
prefix = 'A' + ++this._state.curId + ' ';
this._state.conn.cleartext.write(prefix + cmd + CRLF);
debug('<<SENT>>: ' + prefix + cmd);
}
};

Loading…
Cancel
Save