message_bus 2.0.0.beta.5 → 2.0.0.beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|