imap-client
Advanced tools
Comparing version 0.11.1 to 0.12.0
{ | ||
"name": "imap-client", | ||
"version": "0.11.1", | ||
"version": "0.12.0", | ||
"scripts": { | ||
@@ -5,0 +5,0 @@ "pretest": "dir=$(pwd) && cd node_modules/browserbox/node_modules/tcp-socket/node_modules/node-forge/ && npm install && npm run minify && cd $dir", |
@@ -5,2 +5,4 @@ # imap-client | ||
Needs ES6 Promises, [supply polyfills where necessary](https://github.com/jakearchibald/es6-promise). | ||
## API | ||
@@ -26,3 +28,3 @@ | ||
### #login(callback) and #logout(callback) | ||
### #login() and #logout() | ||
@@ -32,12 +34,12 @@ Log in to an IMAP Session. No-op if already logged in. | ||
``` | ||
imap.login(function() { | ||
imap.login().then(function() { | ||
// yay, we’re logged in | ||
}) | ||
imap.logout(function() { | ||
// yay, we’re logged out | ||
}); | ||
imap.logout().then(function() { | ||
// yay, we’re logged out | ||
}); | ||
``` | ||
### #listenForChanges(callback) and #stopListeningForChanges(callback) | ||
### #listenForChanges() and #stopListeningForChanges() | ||
@@ -49,15 +51,28 @@ Set up a connection dedicated to listening for changes published by the IMAP server on one specific inbox. | ||
path: ‘mailboxpath’ | ||
}, function() { | ||
}).then(function() { | ||
// the audience is listening | ||
... | ||
}) | ||
imap.stopListeningForChanges(function() { | ||
// we’re not listening anymore | ||
}); | ||
imap.stopListeningForChanges().then(function() { | ||
// we’re not listening anymore | ||
}) | ||
``` | ||
### #listWellKnownFolders(callback) | ||
### #listWellKnownFolders() | ||
Lists folders, grouped to folders that are in the following categories: Inbox, Drafts, All, Flagged, Sent, Trash, Junk, Archive, Other. | ||
### #createFolder(path) | ||
Creates a folder... | ||
``` | ||
imap.createFolder({ | ||
path: ['foo', 'bar'] | ||
}).then(function() { | ||
// folder created | ||
}) | ||
``` | ||
### #search(options, callback) | ||
@@ -69,7 +84,7 @@ | ||
imap.search({ | ||
answered: true, | ||
unread: true, | ||
header: ['X-Foobar', '123qweasdzxc'] | ||
}, function(error, uids) { | ||
console.log(‘uids: ‘ + uids.join(‘, ‘)) | ||
answered: true, | ||
unread: true, | ||
header: ['X-Foobar', '123qweasdzxc'] | ||
}).then(function(uids) { | ||
console.log(‘uids: ‘ + uids.join(‘, ‘)) | ||
}); | ||
@@ -84,6 +99,6 @@ ``` | ||
imap.listMessages({ | ||
path: ‘path’, the folder's path | ||
firstUid: 15, (optional) the uid of the first messagem defaults to 1 | ||
lastUid: 30 (optional) the uid of the last message, defaults to ‘*’ | ||
}, function(error, messages) {}) | ||
path: ‘path’, the folder's path | ||
firstUid: 15, (optional) the uid of the first messagem defaults to 1 | ||
lastUid: 30 (optional) the uid of the last message, defaults to ‘*’ | ||
}).then(function(messages) {}) | ||
``` | ||
@@ -111,6 +126,6 @@ | ||
imap.getBodyParts({ | ||
path: 'foo/bar', | ||
uid: someMessage.uid, | ||
bodyParts: someMessage.bodyParts | ||
}, function(err) { | ||
path: 'foo/bar', | ||
uid: someMessage.uid, | ||
bodyParts: someMessage.bodyParts | ||
}).then(function() { | ||
// all done, bodyparts can now be fed to the mailreader | ||
@@ -130,3 +145,3 @@ }) | ||
answered: true/false/undefined // (optional) Marks the message as answered, no action if omitted | ||
}, function (error) { | ||
}).then(function( | ||
// all done | ||
@@ -145,3 +160,3 @@ }); | ||
destination: 'bla/bli' // the destination folder | ||
}, function (error) { | ||
}).then(function( | ||
// all done | ||
@@ -159,3 +174,3 @@ }); | ||
message: '...' // RFC-2822 compliant string | ||
}, function (error) { | ||
}).then(function( | ||
// all done | ||
@@ -173,3 +188,3 @@ }); | ||
uid: someMessage.uid, // the message's uid | ||
}, function (error) { | ||
}).then(function( | ||
// all done | ||
@@ -190,16 +205,16 @@ }); | ||
imap.onSyncUpdate = function(options) { | ||
var updatedMesages = options.list, | ||
updatesMailbox = options.path | ||
var updatedMesages = options.list, | ||
updatesMailbox = options.path | ||
if (options.type === SYNC_TYPE_NEW) { | ||
// new messages available on imap | ||
// updatedMesages is an array of the newly available UIDs | ||
// updatedMesages is an array of the newly available UIDs | ||
} else if (options.type === SYNC_TYPE_DELETED) { | ||
// messages have been deleted | ||
// updatedMesages is an array of the deleted UIDs | ||
// updatedMesages is an array of the deleted UIDs | ||
} else if (options.type === SYNC_TYPE_MSGS) { | ||
// NB! several possible reasons why this could be called. | ||
// updatedMesages is an array of objects | ||
// updatedMesages is an array of objects | ||
// if an object in the array has uid value and flags array, it had a possible flag update | ||
} | ||
} | ||
}; | ||
@@ -206,0 +221,0 @@ ``` |
@@ -521,8 +521,2 @@ (function(factory) { | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Can not list well known folders, cause: Not logged in!'); | ||
}); | ||
} | ||
var wellKnownFolders = { | ||
@@ -542,6 +536,9 @@ Inbox: [], | ||
return self._client.listMailboxes().then(function(mailbox) { | ||
return self._checkOnline().then(function() { | ||
return self._client.listMailboxes(); | ||
}).then(function(mailbox) { | ||
axe.debug(DEBUG_TAG, 'folder list received!'); | ||
walkMailbox(mailbox); | ||
return wellKnownFolders; | ||
}).catch(function(error) { | ||
@@ -564,2 +561,3 @@ axe.error(DEBUG_TAG, 'error listing folders: ' + error + '\n' + error.stack); | ||
folder.type = 'Inbox'; | ||
self._delimiter = mailbox.delimiter; | ||
wellKnownFolders.Inbox.push(folder); | ||
@@ -601,2 +599,79 @@ } else if (mailbox.specialUse === '\\Drafts') { | ||
/** | ||
* Creates a folder with the provided path under the personal namespace | ||
* | ||
* @param {String or Array} options.path | ||
* The folder's path. If path is a hierarchy as an array (e.g. ['foo', 'bar', 'baz'] to create foo/bar/bar), | ||
* will create a hierarchy with all intermediate folders if needed. | ||
* @returns {Promise<Array>} Array of uids for messages matching the search terms | ||
*/ | ||
ImapClient.prototype.createFolder = function(options) { | ||
var self = this, | ||
path = options.path; | ||
if (!Array.isArray(path)) { | ||
path = [path]; | ||
} | ||
return self._checkOnline().then(function() { | ||
// spare the check | ||
if (typeof self._delimiter !== 'undefined' && typeof self._prefix !== 'undefined') { | ||
return; | ||
} | ||
// try to get the namespace prefix and delimiter | ||
return self._client.listNamespaces().then(function(namespaces) { | ||
if (namespaces && namespaces.personal && namespaces.personal[0]) { | ||
// personal namespace is available | ||
self._delimiter = namespaces.personal[0].delimiter; | ||
self._prefix = namespaces.personal[0].prefix.split(self._delimiter).shift(); | ||
return; | ||
} | ||
// no namespaces, falling back to empty prefix | ||
self._prefix = ""; | ||
// if we already have the delimiter, there's no need to retrieve the lengthy folder list | ||
if (self._delimiter) { | ||
return; | ||
} | ||
// find the delimiter by listing the folders | ||
return self._client.listMailboxes().then(function(response) { | ||
findDelimiter(response); | ||
}); | ||
}); | ||
}).then(function() { | ||
if (!self._delimiter) { | ||
throw new Error('Could not determine delimiter for mailbox hierarchy'); | ||
} | ||
if (self._prefix) { | ||
path.unshift(self._prefix); | ||
} | ||
// create path [prefix/]foo/bar/baz | ||
return self._client.createMailbox(path.join(self._delimiter)); | ||
}).catch(function(error) { | ||
axe.error(DEBUG_TAG, 'error creating folder ' + options.path + ': ' + error + '\n' + error.stack); | ||
throw error; | ||
}); | ||
// Helper function to find the hierarchy delimiter from a client.listMailboxes() response | ||
function findDelimiter(mailbox) { | ||
if ((mailbox.path || '').toUpperCase() === 'INBOX') { | ||
// found the INBOX, use its hierarchy delimiter, we're done. | ||
self._delimiter = mailbox.delimiter; | ||
return; | ||
} | ||
if (mailbox.children) { | ||
// walk the child mailboxes recursively | ||
mailbox.children.forEach(findDelimiter); | ||
} | ||
} | ||
}; | ||
/** | ||
* Returns the uids of messages containing the search terms in the options | ||
@@ -614,8 +689,2 @@ * @param {String} options.path The folder's path | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Can not search messages, cause: Not logged in!'); | ||
}); | ||
} | ||
var query = {}, | ||
@@ -651,3 +720,5 @@ queryOptions = { | ||
axe.debug(DEBUG_TAG, 'searching in ' + options.path + ' for ' + Object.keys(query).join(',')); | ||
return client.search(query, queryOptions).then(function(uids) { | ||
return self._checkOnline().then(function() { | ||
return client.search(query, queryOptions); | ||
}).then(function(uids) { | ||
axe.debug(DEBUG_TAG, 'searched in ' + options.path + ' for ' + Object.keys(query).join(',') + ': ' + uids); | ||
@@ -672,8 +743,2 @@ return uids; | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Can not list messages, cause: Not logged in!'); | ||
}); | ||
} | ||
var interval = (options.firstUid || 1) + ':' + (options.lastUid || '*'), | ||
@@ -692,3 +757,5 @@ query = ['uid', 'bodystructure', 'flags', 'envelope', 'body.peek[header.fields (references)]'], | ||
axe.debug(DEBUG_TAG, 'listing messages in ' + options.path + ' for interval ' + interval); | ||
return self._client.listMessages(interval, query, queryOptions).then(function(messages) { | ||
return self._checkOnline().then(function() { | ||
return self._client.listMessages(interval, query, queryOptions); | ||
}).then(function(messages) { | ||
// a message without uid will be ignored as malformed | ||
@@ -763,8 +830,2 @@ messages = messages.filter(function(message) { | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Can not get bodyParts for uid ' + options.uid + ' in folder ' + options.path + ', cause: Not logged in!'); | ||
}); | ||
} | ||
// formulate a query for each text part. for part 2.1 to be parsed, we need 2.1.MIME and 2.1 | ||
@@ -791,3 +852,5 @@ bodyParts.forEach(function(bodyPart) { | ||
axe.debug(DEBUG_TAG, 'retrieving body parts for uid ' + options.uid + ' in folder ' + options.path + ': ' + query); | ||
return self._client.listMessages(interval, query, queryOptions).then(function(messages) { | ||
return self._checkOnline().then(function() { | ||
return self._client.listMessages(interval, query, queryOptions); | ||
}).then(function(messages) { | ||
axe.debug(DEBUG_TAG, 'successfully retrieved body parts for uid ' + options.uid + ' in folder ' + options.path + ': ' + query); | ||
@@ -847,8 +910,2 @@ | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Can not update flags, cause: Not logged in!'); | ||
}); | ||
} | ||
if (options.unread === true) { | ||
@@ -886,8 +943,10 @@ remove.push(READ_FLAG); | ||
axe.debug(DEBUG_TAG, 'updating flags for uid ' + options.uid + ' in folder ' + options.path + ': ' + (remove.length > 0 ? (' removing ' + remove) : '') + (add.length > 0 ? (' adding ' + add) : '')); | ||
return new Promise(function(resolve) { | ||
if (add.length > 0) { | ||
resolve(self._client.setFlags(interval, queryAdd, queryOptions)); | ||
} else { | ||
resolve(); | ||
} | ||
return self._checkOnline().then(function() { | ||
return new Promise(function(resolve) { | ||
if (add.length > 0) { | ||
resolve(self._client.setFlags(interval, queryAdd, queryOptions)); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
}).then(function() { | ||
@@ -921,10 +980,6 @@ if (remove.length > 0) { | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Cannot move message, cause: Not logged in!'); | ||
}); | ||
} | ||
axe.debug(DEBUG_TAG, 'moving uid ' + options.uid + ' from ' + options.path + ' to ' + options.destination); | ||
return self._client.moveMessages(interval, options.destination, queryOptions).then(function() { | ||
return self._checkOnline().then(function() { | ||
return self._client.moveMessages(interval, options.destination, queryOptions); | ||
}).then(function() { | ||
axe.debug(DEBUG_TAG, 'successfully moved uid ' + options.uid + ' from ' + options.path + ' to ' + options.destination); | ||
@@ -947,10 +1002,6 @@ }).catch(function(error) { | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Cannot move message, cause: Not logged in!'); | ||
}); | ||
} | ||
axe.debug(DEBUG_TAG, 'uploading a message of ' + options.message.length + ' bytes to ' + options.path); | ||
return self._client.upload(options.path, options.message).then(function() { | ||
return self._checkOnline().then(function() { | ||
return self._client.upload(options.path, options.message); | ||
}).then(function() { | ||
axe.debug(DEBUG_TAG, 'successfully uploaded message to ' + options.path); | ||
@@ -978,10 +1029,6 @@ }).catch(function(error) { | ||
if (!self._loggedIn) { | ||
return new Promise(function() { | ||
throw new Error('Cannot delete message, cause: Not logged in!'); | ||
}); | ||
} | ||
axe.debug(DEBUG_TAG, 'deleting uid ' + options.uid + ' from ' + options.path); | ||
return self._client.deleteMessages(interval, queryOptions).then(function() { | ||
return self._checkOnline().then(function() { | ||
return self._client.deleteMessages(interval, queryOptions); | ||
}).then(function() { | ||
axe.debug(DEBUG_TAG, 'successfully deleted uid ' + options.uid + ' from ' + options.path); | ||
@@ -1019,2 +1066,14 @@ }).catch(function(error) { | ||
ImapClient.prototype._checkOnline = function() { | ||
var self = this; | ||
return new Promise(function(resolve) { | ||
if (!self._loggedIn) { | ||
throw new Error('Not logged in!'); | ||
} | ||
resolve(); | ||
}); | ||
}; | ||
/* | ||
@@ -1021,0 +1080,0 @@ * Mime Tree Handling |
@@ -94,3 +94,20 @@ (function(factory) { | ||
it('should create folder hierarchy', function(done) { | ||
ic.createFolder({ | ||
path: ['bar', 'baz'] | ||
}).then(function() { | ||
return ic.listWellKnownFolders(); | ||
}).then(function(folders) { | ||
var hasFoo = false; | ||
folders.Other.forEach(function(folder) { | ||
hasFoo = hasFoo || folder.path === 'INBOX.bar.baz'; | ||
}); | ||
expect(hasFoo).to.be.true; | ||
expect(ic._delimiter).to.exist; | ||
expect(ic._prefix).to.exist; | ||
expect(hasFoo).to.be.true; | ||
}).then(done); | ||
}); | ||
it('should upload Message', function(done) { | ||
@@ -97,0 +114,0 @@ var msg = 'MIME-Version: 1.0\r\nDate: Wed, 9 Jul 2014 15:07:47 +0200\r\nDelivered-To: test@test.com\r\nMessage-ID: <CAHftYYQo=5fqbtnv-DazXhL2j5AxVP1nWarjkztn-N9SV91Z2w@mail.gmail.com>\r\nSubject: integration test\r\nFrom: Test Test <test@test.com>\r\nTo: Test Test <test@test.com>\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nintegration test', |
@@ -97,3 +97,2 @@ 'use strict'; | ||
var invocations = 0; // counts the message updates | ||
ic._maxUpdateSize = 1; | ||
@@ -103,8 +102,5 @@ ic.onSyncUpdate = function(options) { | ||
expect(options.list.length).to.equal(1); | ||
expect(options.list.length).to.equal(6); | ||
expect(options.type).to.equal('new'); | ||
if (invocations === 6) { | ||
done(); | ||
} | ||
done(); | ||
}; | ||
@@ -151,2 +147,36 @@ | ||
it('should create folder', function(done) { | ||
ic.createFolder({ | ||
path: 'foo' | ||
}).then(function() { | ||
return ic.listWellKnownFolders(); | ||
}).then(function(folders) { | ||
var hasFoo = false; | ||
folders.Other.forEach(function(folder) { | ||
hasFoo = hasFoo || folder.path === 'foo'; | ||
}); | ||
expect(hasFoo).to.be.true; | ||
expect(ic._delimiter).to.exist; | ||
expect(ic._prefix).to.exist; | ||
}).then(done); | ||
}); | ||
it('should create folder hierarchy', function(done) { | ||
ic.createFolder({ | ||
path: ['bar', 'baz'] | ||
}).then(function() { | ||
return ic.listWellKnownFolders(); | ||
}).then(function(folders) { | ||
var hasFoo = false; | ||
folders.Other.forEach(function(folder) { | ||
hasFoo = hasFoo || folder.path === 'bar/baz'; | ||
}); | ||
expect(hasFoo).to.be.true; | ||
}).then(done); | ||
}); | ||
it('should search messages for header', function(done) { | ||
@@ -153,0 +183,0 @@ ic.search({ |
@@ -28,3 +28,2 @@ (function(factory) { | ||
expect(imap._client).to.equal(bboxMock); | ||
expect(imap._maxUpdateSize).to.equal(0); | ||
@@ -187,2 +186,99 @@ imap._loggedIn = true; | ||
describe('#createFolder', function() { | ||
it('should create folder with namespaces', function(done) { | ||
bboxMock.listNamespaces.returns(resolves({ | ||
"personal": [{ | ||
"prefix": "BLA/", | ||
"delimiter": "/" | ||
}], | ||
"users": false, | ||
"shared": false | ||
})); | ||
bboxMock.createMailbox.withArgs('BLA/foo').returns(resolves()); | ||
imap.createFolder({ | ||
path: 'foo' | ||
}).then(function() { | ||
expect(bboxMock.listNamespaces.calledOnce).to.be.true; | ||
expect(bboxMock.createMailbox.calledOnce).to.be.true; | ||
expect(imap._delimiter).to.exist; | ||
expect(imap._prefix).to.exist; | ||
done(); | ||
}); | ||
}); | ||
it('should create folder without namespaces', function(done) { | ||
bboxMock.listNamespaces.returns(resolves()); | ||
bboxMock.listMailboxes.returns(resolves({ | ||
"root": true, | ||
"children": [{ | ||
"name": "INBOX", | ||
"delimiter": "/", | ||
"path": "INBOX" | ||
}] | ||
})); | ||
bboxMock.createMailbox.withArgs('foo').returns(resolves()); | ||
imap.createFolder({ | ||
path: 'foo' | ||
}).then(function() { | ||
expect(bboxMock.listNamespaces.calledOnce).to.be.true; | ||
expect(bboxMock.createMailbox.calledOnce).to.be.true; | ||
expect(imap._delimiter).to.exist; | ||
expect(imap._prefix).to.exist; | ||
done(); | ||
}); | ||
}); | ||
it('should create folder hierarchy with namespaces', function(done) { | ||
bboxMock.listNamespaces.returns(resolves({ | ||
"personal": [{ | ||
"prefix": "BLA/", | ||
"delimiter": "/" | ||
}], | ||
"users": false, | ||
"shared": false | ||
})); | ||
bboxMock.createMailbox.withArgs('foo/bar').returns(resolves()); | ||
bboxMock.createMailbox.withArgs('foo/baz').returns(resolves()); | ||
imap.createFolder({ | ||
path: ['foo', 'bar'] | ||
}).then(function() { | ||
return imap.createFolder({ | ||
path: ['foo', 'baz'] | ||
}); | ||
}).then(function() { | ||
expect(bboxMock.listNamespaces.calledOnce).to.be.true; | ||
expect(bboxMock.createMailbox.calledTwice).to.be.true; | ||
expect(imap._delimiter).to.exist; | ||
expect(imap._prefix).to.exist; | ||
done(); | ||
}); | ||
}); | ||
it('should create folder hierarchy without namespaces', function(done) { | ||
bboxMock.listNamespaces.returns(resolves()); | ||
bboxMock.listMailboxes.returns(resolves({ | ||
"root": true, | ||
"children": [{ | ||
"name": "INBOX", | ||
"delimiter": "/", | ||
"path": "INBOX" | ||
}] | ||
})); | ||
bboxMock.createMailbox.withArgs('foo').returns(resolves()); | ||
imap.createFolder({ | ||
path: ['foo', 'bar'] | ||
}).then(function() { | ||
expect(bboxMock.listNamespaces.calledOnce).to.be.true; | ||
expect(bboxMock.createMailbox.calledOnce).to.be.true; | ||
expect(imap._delimiter).to.exist; | ||
expect(imap._prefix).to.exist; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#search', function() { | ||
@@ -442,3 +538,8 @@ it('should search answered', function(done) { | ||
path: 'foobar', | ||
uid: 123 | ||
uid: 123, | ||
bodyParts: [{ | ||
partNumber: '1' | ||
}, { | ||
partNumber: '2' | ||
}] | ||
}).catch(function() { | ||
@@ -455,3 +556,3 @@ done(); | ||
}).returns(resolves()); | ||
bboxMock.setFlags.withArgs('123:123', { | ||
@@ -657,3 +758,5 @@ remove: ['\\Seen'] | ||
it('should switch mailboxes', function(done) { | ||
bboxMock.selectMailbox.withArgs('qweasdzxc', {ctx: ctx}).yields(); | ||
bboxMock.selectMailbox.withArgs('qweasdzxc', { | ||
ctx: ctx | ||
}).yields(); | ||
imap._ensurePath('qweasdzxc')(ctx, function(err) { | ||
@@ -667,3 +770,5 @@ expect(err).to.not.exist; | ||
it('should error during switching mailboxes', function(done) { | ||
bboxMock.selectMailbox.withArgs('qweasdzxc', {ctx: ctx}).yields(new Error()); | ||
bboxMock.selectMailbox.withArgs('qweasdzxc', { | ||
ctx: ctx | ||
}).yields(new Error()); | ||
imap._ensurePath('qweasdzxc')(ctx, function(err) { | ||
@@ -670,0 +775,0 @@ expect(err).to.exist; |
110137
2352
241