message_bus 2.0.0.beta.5 → 2.0.0.beta.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of message_bus might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG +8 -0
- data/README.md +5 -5
- data/Rakefile +7 -1
- data/assets/message-bus-ajax.js +44 -0
- data/assets/message-bus.js +56 -39
- data/lib/message_bus/rack/middleware.rb +4 -2
- data/lib/message_bus/version.rb +1 -1
- data/message_bus.gemspec +1 -0
- data/spec/assets/SpecHelper.js +93 -0
- data/spec/assets/message-bus.spec.js +77 -0
- data/spec/assets/support/jasmine.yml +136 -0
- data/spec/lib/message_bus/rack/middleware_spec.rb +10 -1
- data/vendor/assets/javascripts/message-bus.js +56 -39
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 686569cf588c00aa211a970eab14d37cf979c844
|
4
|
+
data.tar.gz: 167ad799942ea8f8de1e945015bae4f687c4629f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6810d52979d00f065b8de475f8cbeb81edbe7ae8fc706d1a818d32ac2f1c36f2f9ee94c3585968beba594dde53064f82a58348c0948a2b0d64326e356880cb32
|
7
|
+
data.tar.gz: 0bd9ac5196a77f53797e834b3e296bdad9328fcc6a28331b5e53456527eb5be8df25de76fd3aa05e960f4927531d19f7bfb8ffafdb163006bb1264273d0987d1
|
data/CHANGELOG
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
29-02-2016
|
2
2
|
|
3
|
+
- Version 2.0.0.beta.6
|
4
|
+
|
5
|
+
- Feature: Support standalone opertion without depending on jQuery @nathanstitt
|
6
|
+
- Feature: Support a noconflict mode @nathanstitt
|
7
|
+
- Feature: Support JSON POST payload @nathanstitt
|
8
|
+
|
9
|
+
29-02-2016
|
10
|
+
|
3
11
|
- Version 2.0.0.beta.5
|
4
12
|
|
5
13
|
- Fix: JavaScript unsubscribe was not updating publicly visible MessageBus.callbacks @sam
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ Live chat demo per [examples/chat](https://github.com/SamSaffron/message_bus/tre
|
|
20
20
|
|
21
21
|
## Is this used in production at scale?
|
22
22
|
|
23
|
-
**Yes**, MessageBus was extracted out of [Discourse](http://www.discourse.org/) and is used in thousands of production Discourse sites at scale.
|
23
|
+
**Yes**, MessageBus was extracted out of [Discourse](http://www.discourse.org/) and is used in thousands of production Discourse sites at scale.
|
24
24
|
|
25
25
|
## Installation
|
26
26
|
|
@@ -174,7 +174,7 @@ backgroundCallbackInterval|60000|Interval to poll when long polling is disabled
|
|
174
174
|
maxPollInterval|180000|If request to the server start failing, MessageBus will backoff, this is the upper limit of the backoff.
|
175
175
|
alwaysLongPoll|false|For debugging you may want to disable the "is browser in background" check and always long-poll
|
176
176
|
baseUrl|/|If message bus is mounted in a subdirectory of different domain, you may configure it to perform requests there
|
177
|
-
ajax|$.ajax|
|
177
|
+
ajax|$.ajax or XMLHttpRequest|MessageBus will first attempt to use jQuery and then fallback to a plain XMLHttpRequest version that's contained in the `messsage-bus-ajax.js` file. `messsage-bus-ajax.js` must be loaded after `messsage-bus.js` for it to be used.
|
178
178
|
|
179
179
|
**API**:
|
180
180
|
|
@@ -192,6 +192,8 @@ ajax|$.ajax|The only dependency on jQuery, you may set up a custom ajax function
|
|
192
192
|
|
193
193
|
`MessageBus.unsubscribe(channel,func)` : Unsubscribe callback from a particular channel
|
194
194
|
|
195
|
+
`MessageBus.noConflict()` : Removes MessageBus from the global namespace by replacing it with whatever was present before MessageBus was loaded. Returns a reference to the MessageBus object.
|
196
|
+
|
195
197
|
## Running tests
|
196
198
|
|
197
199
|
To run tests you need both Postgres and Redis installed. By default we will connect to the database `message_bus_test` with the current username. If you wish to override this:
|
@@ -288,7 +290,7 @@ after_fork do |server, worker|
|
|
288
290
|
end
|
289
291
|
```
|
290
292
|
|
291
|
-
###
|
293
|
+
###
|
292
294
|
|
293
295
|
## Want to help?
|
294
296
|
|
@@ -299,5 +301,3 @@ If you are looking to contribute to this project here are some ideas
|
|
299
301
|
- Improve and properly document admin dashboard (add opt-in stats, better diagnostics into queues)
|
300
302
|
- Improve general documentation (Add examples, refine existing examples)
|
301
303
|
- Make MessageBus a nice website
|
302
|
-
|
303
|
-
|
data/Rakefile
CHANGED
@@ -3,6 +3,10 @@ require 'rake/testtask'
|
|
3
3
|
require 'bundler'
|
4
4
|
require 'bundler/gem_tasks'
|
5
5
|
require 'bundler/setup'
|
6
|
+
require 'jasmine'
|
7
|
+
|
8
|
+
ENV['JASMINE_CONFIG_PATH'] ||= File.join(Dir.pwd, 'spec', 'assets', 'support', 'jasmine.yml')
|
9
|
+
load 'jasmine/tasks/jasmine.rake'
|
6
10
|
|
7
11
|
Bundler.require(:default, :test)
|
8
12
|
|
@@ -17,7 +21,9 @@ run_spec = proc do |backend|
|
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
|
-
task :spec => [:spec_redis, :spec_postgres, :spec_memory]
|
24
|
+
task :spec => [:spec_redis, :spec_postgres, :spec_memory, :spec_client_js]
|
25
|
+
|
26
|
+
task :spec_client_js => 'jasmine:ci'
|
21
27
|
|
22
28
|
task :spec_redis do
|
23
29
|
run_spec.call('redis')
|
@@ -0,0 +1,44 @@
|
|
1
|
+
// A bare-bones implementation of $.ajax that MessageBus will use
|
2
|
+
// as a fallback if jQuery is not present
|
3
|
+
//
|
4
|
+
// Only implements methods & options used by MessageBus
|
5
|
+
(function(global, undefined) {
|
6
|
+
'use strict';
|
7
|
+
if (!global.MessageBus){
|
8
|
+
throw new Error("MessageBus must be loaded before the ajax adapter");
|
9
|
+
}
|
10
|
+
|
11
|
+
var cacheBuster = Math.random() * 10000 | 0;
|
12
|
+
|
13
|
+
global.MessageBus.ajax = function(options){
|
14
|
+
var XHRImpl = (global.MessageBus && global.MessageBus.xhrImplementation) || global.XMLHttpRequest;
|
15
|
+
var xhr = new XHRImpl();
|
16
|
+
xhr.dataType = options.dataType;
|
17
|
+
var url = options.url;
|
18
|
+
if (!options.cache){
|
19
|
+
url += ((-1 == url.indexOf('?')) ? '?' : '&') + '_=' + (cacheBuster++)
|
20
|
+
}
|
21
|
+
xhr.open('POST', url);
|
22
|
+
for (var name in options.headers){
|
23
|
+
xhr.setRequestHeader(name, options.headers[name]);
|
24
|
+
}
|
25
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
26
|
+
if (options.messageBus.chunked){
|
27
|
+
options.messageBus.onProgressListener(xhr);
|
28
|
+
}
|
29
|
+
xhr.onreadystatechange = function(){
|
30
|
+
if (xhr.readyState === 4){
|
31
|
+
var status = xhr.status;
|
32
|
+
if (status >= 200 && status < 300 || status === 304){
|
33
|
+
options.success(xhr.responseText);
|
34
|
+
} else {
|
35
|
+
options.error(xhr, xhr.statusText);
|
36
|
+
}
|
37
|
+
options.complete();
|
38
|
+
}
|
39
|
+
}
|
40
|
+
xhr.send(JSON.stringify(options.data));
|
41
|
+
return xhr;
|
42
|
+
};
|
43
|
+
|
44
|
+
})(window);
|
data/assets/message-bus.js
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
/*jshint bitwise: false*/
|
2
|
-
|
2
|
+
(function(global, document, undefined) {
|
3
|
+
'use strict';
|
4
|
+
var previousMessageBus = global.MessageBus;
|
3
5
|
|
4
|
-
window.MessageBus = (function() {
|
5
6
|
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
6
7
|
var callbacks, clientId, failCount, shouldLongPoll, queue, responseCallbacks, uniqueId, baseUrl;
|
7
|
-
var me, started, stopped, longPoller, pollTimeout, paused, later;
|
8
|
+
var me, started, stopped, longPoller, pollTimeout, paused, later, jQuery, interval, chunkedBackoff;
|
8
9
|
|
9
10
|
uniqueId = function() {
|
10
11
|
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
@@ -25,10 +26,9 @@ window.MessageBus = (function() {
|
|
25
26
|
paused = false;
|
26
27
|
later = [];
|
27
28
|
chunkedBackoff = 0;
|
28
|
-
|
29
|
+
jQuery = global.jQuery;
|
29
30
|
var hiddenProperty;
|
30
31
|
|
31
|
-
|
32
32
|
(function(){
|
33
33
|
var prefixes = ["","webkit","ms","moz"];
|
34
34
|
for(var i=0; i<prefixes.length; i++) {
|
@@ -161,38 +161,55 @@ window.MessageBus = (function() {
|
|
161
161
|
}
|
162
162
|
};
|
163
163
|
|
164
|
+
var setOnProgressListener = function(xhr) {
|
165
|
+
var position = 0;
|
166
|
+
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
167
|
+
// this is messing with us, so just backoff from using chunked for now
|
168
|
+
var chunkedTimeout = setTimeout(disableChunked,3000);
|
169
|
+
xhr.onprogress = function () {
|
170
|
+
clearTimeout(chunkedTimeout);
|
171
|
+
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
172
|
+
// not chunked we are sending json back
|
173
|
+
chunked = false;
|
174
|
+
return;
|
175
|
+
}
|
176
|
+
position = handle_progress(xhr.responseText, position);
|
177
|
+
}
|
178
|
+
};
|
179
|
+
if (!me.ajax){
|
180
|
+
throw new Error("Either jQuery or the ajax adapter must be loaded");
|
181
|
+
}
|
164
182
|
var req = me.ajax({
|
165
|
-
url: me.baseUrl + "message-bus/" + me.clientId + "/poll
|
183
|
+
url: me.baseUrl + "message-bus/" + me.clientId + "/poll" + (!longPoll ? "?dlp=t" : ""),
|
166
184
|
data: data,
|
167
185
|
cache: false,
|
168
186
|
dataType: dataType,
|
169
187
|
type: 'POST',
|
170
188
|
headers: headers,
|
189
|
+
messageBus: {
|
190
|
+
chunked: chunked,
|
191
|
+
onProgressListener: function(xhr) {
|
192
|
+
var position = 0;
|
193
|
+
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
194
|
+
// this is messing with us, so just backoff from using chunked for now
|
195
|
+
var chunkedTimeout = setTimeout(disableChunked,3000);
|
196
|
+
return xhr.onprogress = function () {
|
197
|
+
clearTimeout(chunkedTimeout);
|
198
|
+
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
199
|
+
chunked = false; // not chunked, we are sending json back
|
200
|
+
} else {
|
201
|
+
position = handle_progress(xhr.responseText, position);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
}
|
205
|
+
},
|
171
206
|
xhr: function() {
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
var position = 0;
|
179
|
-
|
180
|
-
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
181
|
-
// this is messing with us, so just backoff from using chunked for now
|
182
|
-
var chunkedTimeout = setTimeout(disableChunked,3000);
|
183
|
-
|
184
|
-
xhr.onprogress = function () {
|
185
|
-
clearTimeout(chunkedTimeout);
|
186
|
-
|
187
|
-
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
188
|
-
// not chunked we are sending json back
|
189
|
-
chunked = false;
|
190
|
-
return;
|
191
|
-
}
|
192
|
-
position = handle_progress(xhr.responseText, position);
|
193
|
-
}
|
194
|
-
|
195
|
-
return xhr;
|
207
|
+
var xhr = jQuery.ajaxSettings.xhr();
|
208
|
+
if (!chunked) {
|
209
|
+
return xhr;
|
210
|
+
}
|
211
|
+
this.messageBus.onProgressListener(xhr);
|
212
|
+
return xhr;
|
196
213
|
},
|
197
214
|
success: function(messages) {
|
198
215
|
if (!chunked) {
|
@@ -257,10 +274,11 @@ window.MessageBus = (function() {
|
|
257
274
|
clientId: clientId,
|
258
275
|
alwaysLongPoll: false,
|
259
276
|
baseUrl: baseUrl,
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
277
|
+
ajax: (jQuery && jQuery.ajax),
|
278
|
+
noConflict: function(){
|
279
|
+
global.MessageBus = global.MessageBus.previousMessageBus;
|
280
|
+
return this;
|
281
|
+
},
|
264
282
|
diagnostics: function(){
|
265
283
|
console.log("Stopped: " + stopped + " Started: " + started);
|
266
284
|
console.log("Current callbacks");
|
@@ -317,7 +335,7 @@ window.MessageBus = (function() {
|
|
317
335
|
|
318
336
|
// monitor visibility, issue a new long poll when the page shows
|
319
337
|
if(document.addEventListener && 'hidden' in document){
|
320
|
-
me.visibilityEvent = document.addEventListener('visibilitychange', function(){
|
338
|
+
me.visibilityEvent = global.document.addEventListener('visibilitychange', function(){
|
321
339
|
if(!document.hidden && !me.longPoll && pollTimeout){
|
322
340
|
clearTimeout(pollTimeout);
|
323
341
|
pollTimeout = null;
|
@@ -366,7 +384,7 @@ window.MessageBus = (function() {
|
|
366
384
|
|
367
385
|
for (var i=callbacks.length-1; i>=0; i--) {
|
368
386
|
|
369
|
-
callback = callbacks[i];
|
387
|
+
var callback = callbacks[i];
|
370
388
|
var keep;
|
371
389
|
|
372
390
|
if (glob) {
|
@@ -392,6 +410,5 @@ window.MessageBus = (function() {
|
|
392
410
|
return removed;
|
393
411
|
}
|
394
412
|
};
|
395
|
-
|
396
|
-
|
397
|
-
})();
|
413
|
+
global.MessageBus = me;
|
414
|
+
})(window, document);
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
# our little message bus, accepts long polling and polling
|
2
4
|
module MessageBus::Rack; end
|
3
5
|
|
@@ -93,7 +95,8 @@ class MessageBus::Rack::Middleware
|
|
93
95
|
end
|
94
96
|
else
|
95
97
|
request = Rack::Request.new(env)
|
96
|
-
request.
|
98
|
+
data = request.content_type.include?('application/json') ? JSON.parse(request.body.read) : request.POST
|
99
|
+
data.each do |k,v|
|
97
100
|
if k == "__seq".freeze
|
98
101
|
client.seq = v.to_i
|
99
102
|
else
|
@@ -203,4 +206,3 @@ class MessageBus::Rack::Middleware
|
|
203
206
|
}
|
204
207
|
end
|
205
208
|
end
|
206
|
-
|
data/lib/message_bus/version.rb
CHANGED
data/message_bus.gemspec
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
var message_id = 1;
|
2
|
+
var SEPARATOR = "\r\n|\r\n";
|
3
|
+
|
4
|
+
var encodeChunks = function(xhr, chunks) {
|
5
|
+
if (!chunks || !chunks.length){
|
6
|
+
return '';
|
7
|
+
}
|
8
|
+
for (var i=0;i<chunks.length;i++) {
|
9
|
+
var chunk = chunks[i];
|
10
|
+
chunk.global_id = Math.random() * 10000 | 0;
|
11
|
+
chunk.message_id = message_id++;
|
12
|
+
}
|
13
|
+
if (xhr.onprogress){ // using longPoll if onprogress is set
|
14
|
+
var responses = []
|
15
|
+
for (var i=0;i<chunks.length;i++) {
|
16
|
+
responses.push( JSON.stringify([chunk]) );
|
17
|
+
}
|
18
|
+
return responses.join(SEPARATOR) + SEPARATOR;
|
19
|
+
} else {
|
20
|
+
return chunks;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
beforeEach(function () {
|
25
|
+
var spec = this;
|
26
|
+
|
27
|
+
function MockedXMLHttpRequest(options){ this.options = options; }
|
28
|
+
MockedXMLHttpRequest.prototype.send = function(){
|
29
|
+
this.readyState = 4
|
30
|
+
this.responseText = encodeChunks(this, spec.responseChunks);
|
31
|
+
this.statusText = 'OK';
|
32
|
+
this.status = 200;
|
33
|
+
if (this.onprogress){ this.onprogress(); }
|
34
|
+
this.onreadystatechange()
|
35
|
+
}
|
36
|
+
MockedXMLHttpRequest.prototype.open = function(){ }
|
37
|
+
MockedXMLHttpRequest.prototype.abort = function(){ }
|
38
|
+
MockedXMLHttpRequest.prototype.setRequestHeader = function(){ }
|
39
|
+
MockedXMLHttpRequest.prototype.getResponseHeader = function(){
|
40
|
+
return 'text/plain; charset=utf-8';
|
41
|
+
}
|
42
|
+
MessageBus.xhrImplementation = MockedXMLHttpRequest
|
43
|
+
this.MockedXMLHttpRequest = MockedXMLHttpRequest
|
44
|
+
MessageBus.start()
|
45
|
+
this.responseChunks = [
|
46
|
+
{channel: '/test', data: {password: 'MessageBusRocks!'}}
|
47
|
+
];
|
48
|
+
|
49
|
+
});
|
50
|
+
|
51
|
+
window.testMB = function(description, testFn, path, data){
|
52
|
+
this.responseChunks = [
|
53
|
+
{channel: path || '/test', data: data || {password: 'MessageBusRocks!'}}
|
54
|
+
];
|
55
|
+
it(description, function(done){
|
56
|
+
spec = this;
|
57
|
+
promisy = {
|
58
|
+
finally: function(fn){
|
59
|
+
this.resolve = fn;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
this.perform = function(specFn){
|
63
|
+
var xhrRequest = null;
|
64
|
+
spyOn(this.MockedXMLHttpRequest.prototype, 'open').and.callFake(function(method, url){
|
65
|
+
xhrRequest = this;
|
66
|
+
xhrRequest.url = url
|
67
|
+
xhrRequest.method = method
|
68
|
+
spec.MockedXMLHttpRequest.prototype.open.and.callThrough(this, method, url);
|
69
|
+
})
|
70
|
+
MessageBus.subscribe(path || '/test', function(message){
|
71
|
+
try {
|
72
|
+
specFn.call(spec, message, xhrRequest);
|
73
|
+
} catch( error ){
|
74
|
+
promisy.resolve.call(spec);
|
75
|
+
throw(error);
|
76
|
+
}
|
77
|
+
promisy.resolve.call(spec);
|
78
|
+
done();
|
79
|
+
});
|
80
|
+
return promisy;
|
81
|
+
};
|
82
|
+
testFn.call(this);
|
83
|
+
});
|
84
|
+
|
85
|
+
}
|
86
|
+
|
87
|
+
afterEach(function(){
|
88
|
+
MessageBus.stop()
|
89
|
+
if (MessageBus.longPoll){
|
90
|
+
MessageBus.longPoll.abort();
|
91
|
+
}
|
92
|
+
MessageBus.callbacks.splice(0, MessageBus.callbacks.length)
|
93
|
+
});
|
@@ -0,0 +1,77 @@
|
|
1
|
+
describe("Messagebus", function() {
|
2
|
+
|
3
|
+
it("submits change requests", function(done){
|
4
|
+
spyOn(this.MockedXMLHttpRequest.prototype, 'send').and.callThrough();
|
5
|
+
var spec = this;
|
6
|
+
MessageBus.subscribe('/test', function(){
|
7
|
+
expect(spec.MockedXMLHttpRequest.prototype.send)
|
8
|
+
.toHaveBeenCalled()
|
9
|
+
var req = JSON.parse(spec.MockedXMLHttpRequest.prototype.send.calls.argsFor(0)[0]);
|
10
|
+
expect(req['/test']).toEqual(-1)
|
11
|
+
expect(req['__seq']).not.toBeUndefined();
|
12
|
+
done()
|
13
|
+
});
|
14
|
+
});
|
15
|
+
|
16
|
+
it("calls callbacks", function(done){
|
17
|
+
MessageBus.subscribe('/test', function(message){
|
18
|
+
expect(message.password).toEqual('MessageBusRocks!');
|
19
|
+
done();
|
20
|
+
});
|
21
|
+
});
|
22
|
+
|
23
|
+
it('stores messages when paused, then delivers them when resumed', function(done){
|
24
|
+
MessageBus.pause()
|
25
|
+
spyOn(this.MockedXMLHttpRequest.prototype, 'send').and.callThrough();
|
26
|
+
var spec = this;
|
27
|
+
var onMessageSpy = jasmine.createSpy('onMessageSpy');
|
28
|
+
MessageBus.subscribe('/test', onMessageSpy);
|
29
|
+
setTimeout(function(){
|
30
|
+
expect(spec.MockedXMLHttpRequest.prototype.send).toHaveBeenCalled()
|
31
|
+
expect(onMessageSpy).not.toHaveBeenCalled()
|
32
|
+
MessageBus.resume()
|
33
|
+
}, 510) // greater than delayPollTimeout of 500
|
34
|
+
setTimeout(function(){
|
35
|
+
expect(onMessageSpy).toHaveBeenCalled()
|
36
|
+
done()
|
37
|
+
}, 550) // greater than first timeout above
|
38
|
+
});
|
39
|
+
|
40
|
+
it('can unsubscribe from callbacks', function(done){
|
41
|
+
var onMessageSpy = jasmine.createSpy('onMessageSpy');
|
42
|
+
MessageBus.subscribe('/test', onMessageSpy);
|
43
|
+
MessageBus.unsubscribe('/test', onMessageSpy);
|
44
|
+
MessageBus.subscribe('/test', function(){
|
45
|
+
expect(onMessageSpy).not.toHaveBeenCalled()
|
46
|
+
done()
|
47
|
+
});
|
48
|
+
});
|
49
|
+
|
50
|
+
testMB('sets dlp paramater when longPolling is disabled', function(){
|
51
|
+
MessageBus.enableLongPolling = false
|
52
|
+
this.perform(function(message, xhr){
|
53
|
+
expect(xhr.url).toMatch("dlp=t");
|
54
|
+
}).finally(function(){
|
55
|
+
MessageBus.enableLongPolling = true
|
56
|
+
})
|
57
|
+
});
|
58
|
+
|
59
|
+
testMB('respects baseUrl setting', function(){
|
60
|
+
MessageBus.baseUrl = "/a/test/base/url/";
|
61
|
+
this.perform(function(message, xhr){
|
62
|
+
expect(xhr.url).toMatch("/a/test/base/url/");
|
63
|
+
}).finally(function(){
|
64
|
+
MessageBus.baseUrl = "/";
|
65
|
+
})
|
66
|
+
});
|
67
|
+
|
68
|
+
it('removes itself from root namespace when noConflict is called', function(){
|
69
|
+
expect(window.MessageBus).not.toBeUndefined();
|
70
|
+
var mb = window.MessageBus;
|
71
|
+
expect(mb).toEqual(window.MessageBus.noConflict());
|
72
|
+
expect(window.MessageBus).toBeUndefined();
|
73
|
+
// reset it so afterEach has something to work on
|
74
|
+
window.MessageBus = mb;
|
75
|
+
});
|
76
|
+
|
77
|
+
});
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# src_files
|
2
|
+
#
|
3
|
+
# Return an array of filepaths relative to src_dir to include before jasmine specs.
|
4
|
+
# Default: []
|
5
|
+
#
|
6
|
+
# EXAMPLE:
|
7
|
+
#
|
8
|
+
# src_files:
|
9
|
+
# - lib/source1.js
|
10
|
+
# - lib/source2.js
|
11
|
+
# - dist/**/*.js
|
12
|
+
#
|
13
|
+
src_files:
|
14
|
+
- assets/message-bus.js
|
15
|
+
- assets/message-bus-ajax.js
|
16
|
+
|
17
|
+
|
18
|
+
# stylesheets
|
19
|
+
#
|
20
|
+
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
|
21
|
+
# Default: []
|
22
|
+
#
|
23
|
+
# EXAMPLE:
|
24
|
+
#
|
25
|
+
# stylesheets:
|
26
|
+
# - css/style.css
|
27
|
+
# - stylesheets/*.css
|
28
|
+
#
|
29
|
+
# stylesheets:
|
30
|
+
# - stylesheets/**/*.css
|
31
|
+
|
32
|
+
# helpers
|
33
|
+
#
|
34
|
+
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
|
35
|
+
# Default: ["helpers/**/*.js"]
|
36
|
+
#
|
37
|
+
# EXAMPLE:
|
38
|
+
#
|
39
|
+
# helpers:
|
40
|
+
# - helpers/**/*.js
|
41
|
+
#
|
42
|
+
helpers:
|
43
|
+
- 'SpecHelper.js'
|
44
|
+
|
45
|
+
# spec_files
|
46
|
+
#
|
47
|
+
# Return an array of filepaths relative to spec_dir to include.
|
48
|
+
# Default: ["**/*[sS]pec.js"]
|
49
|
+
#
|
50
|
+
# EXAMPLE:
|
51
|
+
#
|
52
|
+
# spec_files:
|
53
|
+
# - **/*[sS]pec.js
|
54
|
+
#
|
55
|
+
spec_files:
|
56
|
+
- '**/*[sS]pec.js'
|
57
|
+
|
58
|
+
# src_dir
|
59
|
+
#
|
60
|
+
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
|
61
|
+
# Default: project root
|
62
|
+
#
|
63
|
+
# EXAMPLE:
|
64
|
+
#
|
65
|
+
# src_dir: public
|
66
|
+
#
|
67
|
+
src_dir:
|
68
|
+
|
69
|
+
# spec_dir
|
70
|
+
#
|
71
|
+
# Spec directory path. Your spec_files must be returned relative to this path.
|
72
|
+
# Default: spec/javascripts
|
73
|
+
#
|
74
|
+
# EXAMPLE:
|
75
|
+
#
|
76
|
+
# spec_dir: spec/javascripts
|
77
|
+
#
|
78
|
+
spec_dir: spec/assets
|
79
|
+
|
80
|
+
# spec_helper
|
81
|
+
#
|
82
|
+
# Ruby file that Jasmine server will require before starting.
|
83
|
+
# Returned relative to your root path
|
84
|
+
# Default spec/javascripts/support/jasmine_helper.rb
|
85
|
+
#
|
86
|
+
# EXAMPLE:
|
87
|
+
#
|
88
|
+
# spec_helper: spec/javascripts/support/jasmine_helper.rb
|
89
|
+
#
|
90
|
+
# spec_helper: spec/javascripts/support/jasmine_helper.rb
|
91
|
+
|
92
|
+
# boot_dir
|
93
|
+
#
|
94
|
+
# Boot directory path. Your boot_files must be returned relative to this path.
|
95
|
+
# Default: Built in boot file
|
96
|
+
#
|
97
|
+
# EXAMPLE:
|
98
|
+
#
|
99
|
+
# boot_dir: spec/javascripts/support/boot
|
100
|
+
#
|
101
|
+
boot_dir:
|
102
|
+
|
103
|
+
# boot_files
|
104
|
+
#
|
105
|
+
# Return an array of filepaths relative to boot_dir to include in order to boot Jasmine
|
106
|
+
# Default: Built in boot file
|
107
|
+
#
|
108
|
+
# EXAMPLE
|
109
|
+
#
|
110
|
+
# boot_files:
|
111
|
+
# - '**/*.js'
|
112
|
+
#
|
113
|
+
boot_files:
|
114
|
+
|
115
|
+
# rack_options
|
116
|
+
#
|
117
|
+
# Extra options to be passed to the rack server
|
118
|
+
# by default, Port and AccessLog are passed.
|
119
|
+
#
|
120
|
+
# This is an advanced options, and left empty by default
|
121
|
+
#
|
122
|
+
# EXAMPLE
|
123
|
+
#
|
124
|
+
# rack_options:
|
125
|
+
# server: 'thin'
|
126
|
+
|
127
|
+
# random
|
128
|
+
#
|
129
|
+
# Run specs in semi-random order.
|
130
|
+
# Default: false
|
131
|
+
#
|
132
|
+
# EXAMPLE:
|
133
|
+
#
|
134
|
+
# random: true
|
135
|
+
#
|
136
|
+
random:
|
@@ -38,7 +38,7 @@ describe MessageBus::Rack::Middleware do
|
|
38
38
|
|
39
39
|
module LongPolling
|
40
40
|
extend Minitest::Spec::DSL
|
41
|
-
|
41
|
+
|
42
42
|
before do
|
43
43
|
@bus.long_polling_enabled = true
|
44
44
|
end
|
@@ -300,6 +300,15 @@ describe MessageBus::Rack::Middleware do
|
|
300
300
|
parsed.length.must_equal 1
|
301
301
|
end
|
302
302
|
|
303
|
+
it "can decode a JSON encoded request" do
|
304
|
+
id = @bus.last_id('/foo')
|
305
|
+
@bus.publish("/foo", {json: true})
|
306
|
+
post( "/message-bus/1234",
|
307
|
+
JSON.generate({'/foo' => id}),
|
308
|
+
{ "CONTENT_TYPE" => "application/json" })
|
309
|
+
JSON.parse(last_response.body).first["data"].must_equal({'json' => true})
|
310
|
+
end
|
311
|
+
|
303
312
|
describe "messagebus.channels env support" do
|
304
313
|
let(:extra_middleware) do
|
305
314
|
Class.new do
|
@@ -1,10 +1,11 @@
|
|
1
1
|
/*jshint bitwise: false*/
|
2
|
-
|
2
|
+
(function(global, document, undefined) {
|
3
|
+
'use strict';
|
4
|
+
var previousMessageBus = global.MessageBus;
|
3
5
|
|
4
|
-
window.MessageBus = (function() {
|
5
6
|
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
6
7
|
var callbacks, clientId, failCount, shouldLongPoll, queue, responseCallbacks, uniqueId, baseUrl;
|
7
|
-
var me, started, stopped, longPoller, pollTimeout, paused, later;
|
8
|
+
var me, started, stopped, longPoller, pollTimeout, paused, later, jQuery, interval, chunkedBackoff;
|
8
9
|
|
9
10
|
uniqueId = function() {
|
10
11
|
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
@@ -25,10 +26,9 @@ window.MessageBus = (function() {
|
|
25
26
|
paused = false;
|
26
27
|
later = [];
|
27
28
|
chunkedBackoff = 0;
|
28
|
-
|
29
|
+
jQuery = global.jQuery;
|
29
30
|
var hiddenProperty;
|
30
31
|
|
31
|
-
|
32
32
|
(function(){
|
33
33
|
var prefixes = ["","webkit","ms","moz"];
|
34
34
|
for(var i=0; i<prefixes.length; i++) {
|
@@ -161,38 +161,55 @@ window.MessageBus = (function() {
|
|
161
161
|
}
|
162
162
|
};
|
163
163
|
|
164
|
+
var setOnProgressListener = function(xhr) {
|
165
|
+
var position = 0;
|
166
|
+
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
167
|
+
// this is messing with us, so just backoff from using chunked for now
|
168
|
+
var chunkedTimeout = setTimeout(disableChunked,3000);
|
169
|
+
xhr.onprogress = function () {
|
170
|
+
clearTimeout(chunkedTimeout);
|
171
|
+
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
172
|
+
// not chunked we are sending json back
|
173
|
+
chunked = false;
|
174
|
+
return;
|
175
|
+
}
|
176
|
+
position = handle_progress(xhr.responseText, position);
|
177
|
+
}
|
178
|
+
};
|
179
|
+
if (!me.ajax){
|
180
|
+
throw new Error("Either jQuery or the ajax adapter must be loaded");
|
181
|
+
}
|
164
182
|
var req = me.ajax({
|
165
|
-
url: me.baseUrl + "message-bus/" + me.clientId + "/poll
|
183
|
+
url: me.baseUrl + "message-bus/" + me.clientId + "/poll" + (!longPoll ? "?dlp=t" : ""),
|
166
184
|
data: data,
|
167
185
|
cache: false,
|
168
186
|
dataType: dataType,
|
169
187
|
type: 'POST',
|
170
188
|
headers: headers,
|
189
|
+
messageBus: {
|
190
|
+
chunked: chunked,
|
191
|
+
onProgressListener: function(xhr) {
|
192
|
+
var position = 0;
|
193
|
+
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
194
|
+
// this is messing with us, so just backoff from using chunked for now
|
195
|
+
var chunkedTimeout = setTimeout(disableChunked,3000);
|
196
|
+
return xhr.onprogress = function () {
|
197
|
+
clearTimeout(chunkedTimeout);
|
198
|
+
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
199
|
+
chunked = false; // not chunked, we are sending json back
|
200
|
+
} else {
|
201
|
+
position = handle_progress(xhr.responseText, position);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
}
|
205
|
+
},
|
171
206
|
xhr: function() {
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
var position = 0;
|
179
|
-
|
180
|
-
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
181
|
-
// this is messing with us, so just backoff from using chunked for now
|
182
|
-
var chunkedTimeout = setTimeout(disableChunked,3000);
|
183
|
-
|
184
|
-
xhr.onprogress = function () {
|
185
|
-
clearTimeout(chunkedTimeout);
|
186
|
-
|
187
|
-
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
188
|
-
// not chunked we are sending json back
|
189
|
-
chunked = false;
|
190
|
-
return;
|
191
|
-
}
|
192
|
-
position = handle_progress(xhr.responseText, position);
|
193
|
-
}
|
194
|
-
|
195
|
-
return xhr;
|
207
|
+
var xhr = jQuery.ajaxSettings.xhr();
|
208
|
+
if (!chunked) {
|
209
|
+
return xhr;
|
210
|
+
}
|
211
|
+
this.messageBus.onProgressListener(xhr);
|
212
|
+
return xhr;
|
196
213
|
},
|
197
214
|
success: function(messages) {
|
198
215
|
if (!chunked) {
|
@@ -257,10 +274,11 @@ window.MessageBus = (function() {
|
|
257
274
|
clientId: clientId,
|
258
275
|
alwaysLongPoll: false,
|
259
276
|
baseUrl: baseUrl,
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
277
|
+
ajax: (jQuery && jQuery.ajax),
|
278
|
+
noConflict: function(){
|
279
|
+
global.MessageBus = global.MessageBus.previousMessageBus;
|
280
|
+
return this;
|
281
|
+
},
|
264
282
|
diagnostics: function(){
|
265
283
|
console.log("Stopped: " + stopped + " Started: " + started);
|
266
284
|
console.log("Current callbacks");
|
@@ -317,7 +335,7 @@ window.MessageBus = (function() {
|
|
317
335
|
|
318
336
|
// monitor visibility, issue a new long poll when the page shows
|
319
337
|
if(document.addEventListener && 'hidden' in document){
|
320
|
-
me.visibilityEvent = document.addEventListener('visibilitychange', function(){
|
338
|
+
me.visibilityEvent = global.document.addEventListener('visibilitychange', function(){
|
321
339
|
if(!document.hidden && !me.longPoll && pollTimeout){
|
322
340
|
clearTimeout(pollTimeout);
|
323
341
|
pollTimeout = null;
|
@@ -366,7 +384,7 @@ window.MessageBus = (function() {
|
|
366
384
|
|
367
385
|
for (var i=callbacks.length-1; i>=0; i--) {
|
368
386
|
|
369
|
-
callback = callbacks[i];
|
387
|
+
var callback = callbacks[i];
|
370
388
|
var keep;
|
371
389
|
|
372
390
|
if (glob) {
|
@@ -392,6 +410,5 @@ window.MessageBus = (function() {
|
|
392
410
|
return removed;
|
393
411
|
}
|
394
412
|
};
|
395
|
-
|
396
|
-
|
397
|
-
})();
|
413
|
+
global.MessageBus = me;
|
414
|
+
})(window, document);
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: message_bus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.beta.
|
4
|
+
version: 2.0.0.beta.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: jasmine
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: A message bus for rack
|
56
70
|
email:
|
57
71
|
- sam.saffron@gmail.com
|
@@ -73,6 +87,7 @@ files:
|
|
73
87
|
- assets/handlebars.js
|
74
88
|
- assets/index.handlebars
|
75
89
|
- assets/jquery-1.8.2.js
|
90
|
+
- assets/message-bus-ajax.js
|
76
91
|
- assets/message-bus.js
|
77
92
|
- examples/bench/bench.lua
|
78
93
|
- examples/bench/config.ru
|
@@ -102,6 +117,9 @@ files:
|
|
102
117
|
- lib/message_bus/timer_thread.rb
|
103
118
|
- lib/message_bus/version.rb
|
104
119
|
- message_bus.gemspec
|
120
|
+
- spec/assets/SpecHelper.js
|
121
|
+
- spec/assets/message-bus.spec.js
|
122
|
+
- spec/assets/support/jasmine.yml
|
105
123
|
- spec/lib/fake_async_middleware.rb
|
106
124
|
- spec/lib/message_bus/assets/asset_encoding_spec.rb
|
107
125
|
- spec/lib/message_bus/backends/postgres_spec.rb
|
@@ -139,6 +157,9 @@ signing_key:
|
|
139
157
|
specification_version: 4
|
140
158
|
summary: ''
|
141
159
|
test_files:
|
160
|
+
- spec/assets/SpecHelper.js
|
161
|
+
- spec/assets/message-bus.spec.js
|
162
|
+
- spec/assets/support/jasmine.yml
|
142
163
|
- spec/lib/fake_async_middleware.rb
|
143
164
|
- spec/lib/message_bus/assets/asset_encoding_spec.rb
|
144
165
|
- spec/lib/message_bus/backends/postgres_spec.rb
|