browserbox
Advanced tools
Comparing version 0.5.2 to 0.6.0
{ | ||
"name": "browserbox", | ||
"version": "0.5.2", | ||
"version": "0.6.0", | ||
"homepage": "https://github.com/whiteout-io/browserbox", | ||
@@ -5,0 +5,0 @@ "description": "IMAP client for browsers.", |
@@ -277,2 +277,22 @@ # browserbox | ||
## Create mailbox | ||
Create a folder with the given path, automatically handling utf-7 encoding. You | ||
currently need to manually build the path string yourself. (There is potential | ||
for future enhancement to provide assistance.) | ||
If the server indicates a failure but that the folder already exists with the | ||
ALREADYEXISTS response code, the request will be treated as a success. | ||
Example | ||
```javascript | ||
// On a server with a personal namesapce of INBOX and a delimiter of '/', | ||
// create folder Foo. Note that folders using a non-empty personal namespace | ||
// may automatically assume the personal namespace. | ||
client.createMailbox('INBOX/Foo', function callback(err, alreadyExists) {}); | ||
// Do the same on a server where the personal namespace is '' | ||
client.createMailbox('Foo', function callback(err, alreadyExists) {}); | ||
``` | ||
## Select mailbox | ||
@@ -710,2 +730,52 @@ | ||
## State-dependend calls | ||
Calls to Browserbox are queued in FIFO order until they are ready for execution. This may be problematic for calls that depend on a selected mailbox, e.g. `#search` or `#setFlags`. These call can be issued with a `precheck` callback that is invoked **before** the IMAP command is sent to the server. | ||
Example: | ||
```javascript | ||
// search depends on a selected mailbox, e.g. inbox | ||
imap.search({ | ||
header: ['subject', 'hello 3'] | ||
}, { | ||
// add precheck(ctx, next) to the query options | ||
precheck: function(ctx, next) { | ||
// make sure inbox is selected before the search command is run | ||
imap.selectMailbox('inbox', { | ||
ctx: ctx // pass the context parameter received in the precheck callback as a query option to bypass the command queue | ||
}, next); // next should invoked when you're done | ||
}, | ||
byUid: true | ||
}, function(error, result) { | ||
... | ||
}); | ||
``` | ||
A `precheck` callback receives two arguments: | ||
* **ctx** is a context parameter, i.e. a pointer to the current position in the command queue | ||
* **next** callback to be invoked when the precheck is done | ||
Calls issued in a `precheck` callback will be executed *before* the parent call, if you pass the `ctx` parameter received as a argument of the `precheck` callback to the query options. This bypasses the internal FIFO queue and executes the call on the spot! *If the context parameter is left blank, the calls will be queued as usual*. | ||
Invoke `next` once you're done with the `precheck` callback to resume normal operation. | ||
If you want to *remove* a call from the FIFO queue, e.g. because a message is no longer available in a mailbox, pass in an error to the `next` callback. The parent call will not be executed and you will receive the error passed to next in the callback of the parent call. | ||
Example: | ||
```javascript | ||
imap.setFlags('1', ['\\Seen', '$MyFlag'], { | ||
precheck: function(ctx, next) { | ||
next(new Error('Foo')); // this removes the setFlags query from the queue | ||
} | ||
}, function(err, result) { | ||
// err.message -> 'Foo' | ||
... | ||
}); | ||
``` | ||
**NB!** `precheck` callbacks can be also nested! For details, have a look at the integration test that covers this portion of the code. | ||
## Get your hands dirty | ||
@@ -712,0 +782,0 @@ |
@@ -646,3 +646,13 @@ // Copyright (c) 2014 Andris Reinman | ||
this._clientQueue.push(data); | ||
// if we're in priority mode (i.e. we ran commands in a precheck), | ||
// queue any commands BEFORE the command that contianed the precheck, | ||
// otherwise just queue command as usual | ||
var index = data.ctx ? this._clientQueue.indexOf(data.ctx) : -1; | ||
if (index >= 0) { | ||
data.tag += '.p'; | ||
data.request.tag += '.p'; | ||
this._clientQueue.splice(index, 0, data); | ||
} else { | ||
this._clientQueue.push(data); | ||
} | ||
@@ -655,3 +665,3 @@ if (this._canSend) { | ||
/** | ||
* Sends a command from command queue to the server. | ||
* Sends a command from client queue to the server. | ||
*/ | ||
@@ -664,2 +674,44 @@ ImapClient.prototype._sendRequest = function() { | ||
// an operation was made in the precheck, no need to restart the queue manually | ||
this._restartQueue = false; | ||
var command = this._clientQueue[0]; | ||
if (typeof command.precheck === 'function') { | ||
// remember the context | ||
var context = command; | ||
var precheck = context.precheck; | ||
delete context.precheck; | ||
// we need to restart the queue handling if no operation was made in the precheck | ||
this._restartQueue = true; | ||
// invoke the precheck command with a callback to signal that you're | ||
// done with precheck and ready to resume normal operation | ||
precheck(context, function(err) { | ||
// we're done with the precheck | ||
if (!err) { | ||
if (this._restartQueue) { | ||
// we need to restart the queue handling | ||
this._sendRequest(); | ||
} | ||
return; | ||
} | ||
// precheck callback failed, so we remove the initial command | ||
// from the queue, invoke its callback and resume normal operation | ||
var cmd, index = this._clientQueue.indexOf(context); | ||
if (index >= 0) { | ||
cmd = this._clientQueue.splice(index, 1)[0]; | ||
} | ||
if (cmd && cmd.callback) { | ||
cmd.callback(err, function() { | ||
this._canSend = true; | ||
this._sendRequest(); | ||
setTimeout(this._processServerQueue.bind(this), 0); | ||
}.bind(this)); | ||
} | ||
}.bind(this)); | ||
return; | ||
} | ||
this._canSend = false; | ||
@@ -677,2 +729,3 @@ this._currentCommand = this._clientQueue.shift(); | ||
var data = this._currentCommand.data.shift(); | ||
this.send(data + (!this._currentCommand.data.length ? '\r\n' : '')); | ||
@@ -679,0 +732,0 @@ return this.waitDrain; |
@@ -46,2 +46,5 @@ 'use strict'; | ||
raw: "Subject: hello 6\r\n\r\nWorld 6!" | ||
}, { | ||
raw: "Subject: hello 7\r\n\r\nWorld 7!", | ||
uid: 600 | ||
}] | ||
@@ -95,5 +98,2 @@ }, | ||
beforeEach(function(done) { | ||
// don't log in the tests | ||
axe.removeAppender(axe.defaultAppender); | ||
// start imap test server | ||
@@ -462,2 +462,158 @@ var options = { | ||
describe('precheck', function() { | ||
var callCtr; | ||
beforeEach(function() { | ||
callCtr = 0; | ||
}); | ||
it('should use nested precheck calls to cycle through mailboxes, delete mail, move stuff around etc', function(done) { | ||
/* | ||
* start out in [Gmail]/Drafts | ||
* | ||
* execution path #1: | ||
* folder? -> [Gmail]/Spam -> inbox | ||
* set flags on a message (in the inbox) | ||
* | ||
* execution path #2: | ||
* folder? -> inbox | ||
* move 600 to [Gmail]/Drafts | ||
* folder? -> [Gmail]/Drafts | ||
* list messages, make sure uid 1 is there | ||
* delete uid 1 from [Gmail]/Drafts | ||
* | ||
* execution path #3: | ||
* folder? -> inbox | ||
* search for subject 'hello 3', make sure it's there | ||
*/ | ||
imap.selectMailbox('[Gmail]/Drafts', function(err) { | ||
expect(err).to.not.exist; | ||
imap.setFlags('1', ['\\Seen', '$MyFlag'], { | ||
precheck: function(ctx, next) { | ||
imap.selectMailbox('[Gmail]/Spam', { | ||
ctx: ctx | ||
}, function(err) { | ||
expect(err).to.not.exist; | ||
imap.selectMailbox('inbox', { | ||
ctx: ctx | ||
}, next); | ||
}); | ||
} | ||
}, function(err, result) { | ||
expect(err).to.not.exist; | ||
expect(result).to.deep.equal([{ | ||
'#': 1, | ||
'flags': ['\\Seen', '$MyFlag'] | ||
}]); | ||
proceed(); | ||
}); | ||
imap.moveMessages(600, '[Gmail]/Drafts', { | ||
byUid: true, | ||
precheck: function(ctx, next) { | ||
imap.selectMailbox('inbox', { | ||
ctx: ctx | ||
}, next); | ||
} | ||
}, function(err, result) { | ||
expect(err).to.not.exist; | ||
expect(result).to.be.true; | ||
imap.deleteMessages(1, { | ||
byUid: true, | ||
precheck: function(ctx1, next1) { | ||
imap.listMessages("1:*", ["uid", "flags", "envelope", "bodystructure", "body.peek[]"], { | ||
ctx: ctx1, | ||
precheck: function(ctx2, next2) { | ||
imap.selectMailbox('[Gmail]/Drafts', { | ||
ctx: ctx2, | ||
precheck: function(ctx, next3) { | ||
next3(); | ||
} | ||
}, next2); | ||
} | ||
}, function(err, messages) { | ||
expect(err).to.not.exist; | ||
expect(messages[0].uid).to.equal(1); | ||
next1(); | ||
}); | ||
} | ||
}, function(err, result) { | ||
expect(err).to.not.exist; | ||
expect(result).to.be.true; | ||
proceed(); | ||
}); | ||
}); | ||
imap.search({ | ||
header: ['subject', 'hello 3'] | ||
}, { | ||
precheck: function(ctx, next) { | ||
imap.selectMailbox('inbox', { | ||
ctx: ctx | ||
}, next); | ||
}, | ||
byUid: true | ||
}, function(err, result) { | ||
expect(err).to.not.exist; | ||
expect(result).to.deep.equal([555]); | ||
proceed(); | ||
}); | ||
}); | ||
function proceed() { | ||
++callCtr === 3 && done(); | ||
} | ||
}); | ||
it('should error in precheck', function(done) { | ||
/* | ||
* start out in [Gmail]/Drafts | ||
* execution path #1: | ||
* setFlags precheck() should error and never be executed | ||
* | ||
* execution path #2: | ||
* folder? -> inbox | ||
* search for subject 'hello 3', make sure it's there | ||
*/ | ||
imap.selectMailbox('[Gmail]/Drafts', function(err) { | ||
expect(err).to.not.exist; | ||
imap.setFlags('1', ['\\Seen', '$MyFlag'], { | ||
precheck: function(ctx, next) { | ||
next(new Error()); | ||
} | ||
}, function(err, result) { | ||
expect(err).to.exist; | ||
expect(result).to.not.exist; | ||
proceed(); | ||
}); | ||
imap.search({ | ||
header: ['subject', 'hello 3'] | ||
}, { | ||
precheck: function(ctx, next) { | ||
imap.selectMailbox('inbox', { | ||
ctx: ctx | ||
}, next); | ||
}, | ||
byUid: true | ||
}, function(err, result) { | ||
expect(err).to.not.exist; | ||
expect(result).to.deep.equal([555]); | ||
proceed(); | ||
}); | ||
}); | ||
function proceed() { | ||
++callCtr === 2 && done(); | ||
} | ||
}); | ||
}); | ||
}); | ||
@@ -464,0 +620,0 @@ |
@@ -610,2 +610,31 @@ 'use strict'; | ||
}); | ||
it('should run precheck', function(done) { | ||
sinon.stub(client, '_clearIdle'); | ||
client._canSend = true; | ||
client._clientQueue = [{ | ||
request: { | ||
tag: 'W101', | ||
command: 'TEST', | ||
attributes: [{ | ||
type: 'LITERAL', | ||
value: 'abc' | ||
}] | ||
}, | ||
precheck: function(ctx) { | ||
expect(ctx).to.exist; | ||
expect(client._canSend).to.be.true; | ||
client._sendRequest = function() { | ||
expect(client._clientQueue.length).to.equal(2); | ||
expect(client._clientQueue[0].tag).to.include('.p'); | ||
expect(client._clientQueue[0].request.tag).to.include('.p'); | ||
client._clearIdle.restore(); | ||
done(); | ||
}; | ||
client._addToClientQueue({}, undefined, {ctx: ctx}); | ||
} | ||
}]; | ||
client._sendRequest(); | ||
}); | ||
}); | ||
@@ -612,0 +641,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
379485
8716
811