message_bus 3.3.1 → 3.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 659722c1ea3a6a805dbe81099ee2848a11ab7ebffaf8612d119f5062fd740337
4
- data.tar.gz: 4b187421f5fb4bdff177d117b059c05314305729885f5ec520127ad827db5369
3
+ metadata.gz: 52ac95c63b3775df984e4900aff333c2ea9f9bab7e79d16b85909048247fb709
4
+ data.tar.gz: 879fd243f8947150fe8a31307d023b6ca719192568e0def45255dfdddaf1e34f
5
5
  SHA512:
6
- metadata.gz: c345409c8c9c1624b079d28ca166b962f9d64bd79159fc085055df8cb760545372732fd5cf509061b55eb0a427e8ffd7018238a7b49c44f27f4e942d575f0504
7
- data.tar.gz: d92fb533dea19da315c210f7ac96a1029cf2d1709fee3dd919032f221b557cff9ebb3d2556285e5e0c6018ebc4b227ed178fd8658711c5a9ffacf19aeb47edff
6
+ metadata.gz: fe7252d6965628dd75027af80f775126394e6b3a81be01fd03f538f17fe5e7874e997cea552ee2e92d91b7ee47d17059755e7df995c5b8bab24049ce2746e0a8
7
+ data.tar.gz: 2c4ef234bab5ee1701b5bb2d7362929128855dda113a2531dd35f025b9d2be11a2c18a287aa25731e73a9e8f82af898e2f7725a7c9ea4200a390eaff633b4098
data/.eslintrc.js ADDED
@@ -0,0 +1,13 @@
1
+ /*global module*/
2
+ module.exports = {
3
+ env: {
4
+ browser: true,
5
+ es2021: false,
6
+ },
7
+ extends: "eslint:recommended",
8
+ parserOptions: {
9
+ ecmaVersion: 2015,
10
+ sourceType: "module",
11
+ },
12
+ rules: {},
13
+ };
@@ -0,0 +1,54 @@
1
+ name: Message Bus Tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - master
8
+
9
+ env:
10
+ PGHOST: localhost
11
+ PGPORT: 5432
12
+ PGPASSWORD: postgres
13
+ PGUSER: postgres
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ name: Ruby ${{ matrix.ruby }}
19
+ services:
20
+ postgres:
21
+ image: postgres:9.4
22
+ env:
23
+ POSTGRES_PASSWORD: postgres
24
+ ports:
25
+ - 5432:5432
26
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
27
+ strategy:
28
+ matrix:
29
+ ruby: ["2.7", "2.6", "2.5"]
30
+ steps:
31
+ - uses: actions/checkout@v2
32
+ - uses: actions/setup-ruby@v1
33
+ with:
34
+ ruby-version: ${{ matrix.ruby }}
35
+ - name: Bundler cache
36
+ uses: actions/cache@v2
37
+ with:
38
+ path: vendor/bundle
39
+ key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('**/Gemfile.lock') }}
40
+ restore-keys: |
41
+ ${{ runner.os }}-${{ matrix.ruby }}-gems-
42
+ - name: Create Database
43
+ run: |
44
+ createdb message_bus_test
45
+ - name: Setup redis
46
+ uses: shogo82148/actions-setup-redis@v1
47
+ with:
48
+ redis-version: '5.x'
49
+ - name: Setup gems
50
+ run: |
51
+ bundle config path vendor/bundle
52
+ bundle install --jobs 4
53
+ - name: Tests
54
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -18,3 +18,5 @@ tmp
18
18
  *.swp
19
19
  .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml
20
20
  .byebug_history
21
+ node_modules/
22
+ yarn.lock
data/.rubocop.yml CHANGED
@@ -1,6 +1,8 @@
1
1
  inherit_gem:
2
2
  rubocop-discourse: .rubocop.yml
3
-
3
+ inherit_mode:
4
+ merge:
5
+ - Exclude
4
6
  AllCops:
5
7
  Exclude:
6
8
  - 'examples/**/*'
data/CHANGELOG CHANGED
@@ -1,3 +1,50 @@
1
+ 31-05-2021
2
+
3
+ - Version 3.3.6
4
+
5
+ - FEATURE: Introduce support for transport codecs
6
+ - FIX: event subscription leak in JS after start/stop/start sequence
7
+ - FEATURE: MessageBus.onVisibilityChange() can be used to trigger a visiblity change check by hand
8
+
9
+ 28-04-2021
10
+
11
+ - Version 3.3.5
12
+
13
+ - PERF: Optimised CORS preflight handling
14
+ - FEATURE: Enable CORS preflight caching
15
+ - FEATURE: Removed trailing cache buster from message bus polls
16
+ - PERF: Improved delay poll timeout for cases where a tab moves in and out of the background
17
+
18
+ 02-10-2020
19
+
20
+ - Version 3.3.4
21
+
22
+ - FIX: Remove trailing comma incorrectly added in ec60d8865.
23
+
24
+ 18-09-2020
25
+
26
+ - Version 3.3.3
27
+
28
+ - FIX: `queue_in_memory` option not being passed to the backends.
29
+ - FIX: `MessageBus::DistributedCache#publish` should raise on error.
30
+
31
+ On the redis backend, any errors encountered during `MessageBus#publish`
32
+ will add the message into an in memory queue and silently swallow the
33
+ error. While this is behavior is OK for normal message_bus usage, it may
34
+ lead to inconsistency when using `DistributedCache`. If a process
35
+ doesn't publish successfully to another process, it will still update
36
+ its in memory cache leaving the other processes unaware. As such, the
37
+ distributed cache is out of sync and will require another successful
38
+ write to the cache to resync all the caches.
39
+
40
+ 15-09-2020
41
+
42
+ - Version 3.3.2
43
+
44
+ - FIX: In the JavaScript client throw when when lastId is given but is not a number.
45
+ - FEATURE: raise when attempting to publish to invalid targets
46
+ - Log when DistributedCache encounters an error when publishing.
47
+
1
48
  09-06-2020
2
49
 
3
50
  - Version 3.3.1
data/DEV.md ADDED
@@ -0,0 +1,9 @@
1
+ ### How to Publish to NPM
2
+
3
+ 1. First, edit `package.json` and bump the version.
4
+
5
+ 2. Log in to npm `yarn login`
6
+
7
+ 3. Publish: `yarn publish`
8
+
9
+
data/Gemfile CHANGED
@@ -14,10 +14,12 @@ group :test do
14
14
  gem 'rack-test', require: 'rack/test'
15
15
  gem 'jasmine'
16
16
  gem 'puma'
17
+ gem 'm'
17
18
  end
18
19
 
19
20
  group :test, :development do
20
21
  gem 'byebug'
22
+ gem 'oj'
21
23
  end
22
24
 
23
25
  group :development do
data/README.md CHANGED
@@ -10,12 +10,6 @@ MessageBus is implemented as Rack middleware and can be used by any Rails / Sina
10
10
 
11
11
  Read the generated docs: <https://www.rubydoc.info/gems/message_bus>
12
12
 
13
- ## Try it out!
14
-
15
- Live chat demo per [examples/chat](https://github.com/SamSaffron/message_bus/tree/master/examples/chat) is at:
16
-
17
- ### http://chat.samsaffron.com
18
-
19
13
  ## Ruby version support
20
14
 
21
15
  MessageBus only support officially supported versions of Ruby; as of [2018-06-20](https://www.ruby-lang.org/en/news/2018/06/20/support-of-ruby-2-2-has-ended/) this means we only support Ruby version 2.3 and up.
@@ -304,6 +298,11 @@ MessageBus.subscribe("/channel", function(data){
304
298
  MessageBus.subscribe("/channel", function(data){
305
299
  // data shipped from server
306
300
  }, -3);
301
+
302
+ // you will get the entire backlog
303
+ MessageBus.subscribe("/channel", function(data){
304
+ // data shipped from server
305
+ }, 0);
307
306
  ```
308
307
 
309
308
  #### JavaScript Client settings
@@ -436,6 +435,31 @@ MessageBus.configure(backend: :memory)
436
435
 
437
436
  The `:clear_every` option supported by the PostgreSQL backend is also supported by the in-memory backend.
438
437
 
438
+
439
+ ### Transport codecs
440
+
441
+ By default MessageBus serializes messages to the backend using JSON. Under most situation this performs extremely well.
442
+
443
+ In some exceptional cases you may consider a different transport codec. To configure a custom codec use:
444
+
445
+ ```ruby
446
+ MessageBus.configure(transport_codec: codec)
447
+ ```
448
+
449
+ A codec class must implement MessageBus::Codec::Base. Specifically an `encode` and `decode` method.
450
+
451
+ See the `bench` directory for examples where the default JSON codec can perform poorly. A specific examples may be
452
+ attempting to distribute a message to a restricted list of thousands of users. In cases like this you may consider
453
+ using a packed string encoder.
454
+
455
+ Keep in mind, much of MessageBus internals and supporting tools expect data to be converted to JSON and back, if you use a naive (and fast) `Marshal` based codec you may need to limit the features you use. Specifically the Postgresql backend expects the codec never to return a string with `\u0000`, additionally some classes like DistributedCache expect keys to be converted to Strings.
456
+
457
+ Another example may be very large and complicated messages where Oj in compatability mode outperforms JSON. To opt for the Oj codec use:
458
+
459
+ ```
460
+ MessageBus.configure(transport_codec: MessageBus::Codec::Oj.new)
461
+ ```
462
+
439
463
  ### Forking/threading app servers
440
464
 
441
465
  If you're using a forking or threading app server and you're not getting immediate delivery of published messages, you might need to configure your web server to re-connect to the message_bus backend
data/Rakefile CHANGED
@@ -94,5 +94,5 @@ desc "Run all tests, link checks and confirms documentation compiles without err
94
94
  task default: [:spec, :rubocop, :test_doc]
95
95
 
96
96
  Rake::Task['release'].enhance do
97
- sh "npm publish"
97
+ sh "yarn publish"
98
98
  end
@@ -8,17 +8,11 @@
8
8
  throw new Error("MessageBus must be loaded before the ajax adapter");
9
9
  }
10
10
 
11
- var cacheBuster = Math.random() * 10000 | 0;
12
-
13
11
  global.MessageBus.ajax = function(options){
14
12
  var XHRImpl = (global.MessageBus && global.MessageBus.xhrImplementation) || global.XMLHttpRequest;
15
13
  var xhr = new XHRImpl();
16
14
  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);
15
+ xhr.open('POST', options.url);
22
16
  for (var name in options.headers){
23
17
  xhr.setRequestHeader(name, options.headers[name]);
24
18
  }
@@ -1,9 +1,9 @@
1
- /*jshint bitwise: false*/
1
+ /*global define, jQuery*/
2
2
 
3
3
  (function (root, factory) {
4
- if (typeof define === 'function' && define.amd) {
4
+ if (typeof define === "function" && define.amd) {
5
5
  // AMD. Register as an anonymous module.
6
- define([], function (b) {
6
+ define([], function () {
7
7
  // Also create a global in case some scripts
8
8
  // that are loaded still are looking for
9
9
  // a global even when an AMD loader is in use.
@@ -13,12 +13,12 @@
13
13
  // Browser globals
14
14
  root.MessageBus = factory();
15
15
  }
16
- }(typeof self !== 'undefined' ? self : this, function () {
16
+ })(typeof self !== "undefined" ? self : this, function () {
17
17
  "use strict";
18
18
 
19
19
  // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
20
- var uniqueId = function() {
21
- return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(c) {
20
+ var uniqueId = function () {
21
+ return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
22
22
  var r = (Math.random() * 16) | 0;
23
23
  var v = c === "x" ? r : (r & 0x3) | 0x8;
24
24
  return v.toString(16);
@@ -31,8 +31,6 @@
31
31
  var started = false;
32
32
  var clientId = uniqueId();
33
33
  var callbacks = [];
34
- var queue = [];
35
- var interval = null;
36
34
  var failCount = 0;
37
35
  var baseUrl = "/";
38
36
  var paused = false;
@@ -44,7 +42,7 @@
44
42
  var totalAjaxCalls = 0;
45
43
  var lastAjax;
46
44
 
47
- var isHidden = (function() {
45
+ var isHidden = (function () {
48
46
  var prefixes = ["", "webkit", "ms", "moz"];
49
47
  var hiddenProperty;
50
48
  for (var i = 0; i < prefixes.length; i++) {
@@ -55,7 +53,7 @@
55
53
  }
56
54
  }
57
55
 
58
- return function() {
56
+ return function () {
59
57
  if (hiddenProperty !== undefined) {
60
58
  return document[hiddenProperty];
61
59
  } else {
@@ -64,7 +62,7 @@
64
62
  };
65
63
  })();
66
64
 
67
- var hasLocalStorage = (function() {
65
+ var hasLocalStorage = (function () {
68
66
  try {
69
67
  localStorage.setItem("mbTestLocalStorage", Date.now());
70
68
  localStorage.removeItem("mbTestLocalStorage");
@@ -74,13 +72,13 @@
74
72
  }
75
73
  })();
76
74
 
77
- var updateLastAjax = function() {
75
+ var updateLastAjax = function () {
78
76
  if (hasLocalStorage) {
79
77
  localStorage.setItem("__mbLastAjax", Date.now());
80
78
  }
81
79
  };
82
80
 
83
- var hiddenTabShouldWait = function() {
81
+ var hiddenTabShouldWait = function () {
84
82
  if (hasLocalStorage && isHidden()) {
85
83
  var lastAjaxCall = parseInt(localStorage.getItem("__mbLastAjax"), 10);
86
84
  var deltaAjax = Date.now() - lastAjaxCall;
@@ -91,20 +89,21 @@
91
89
  };
92
90
 
93
91
  var hasonprogress = new XMLHttpRequest().onprogress === null;
94
- var allowChunked = function() {
92
+ var allowChunked = function () {
95
93
  return me.enableChunkedEncoding && hasonprogress;
96
94
  };
97
95
 
98
- var shouldLongPoll = function() {
96
+ var shouldLongPoll = function () {
99
97
  return (
100
98
  me.alwaysLongPoll ||
101
99
  (me.shouldLongPollCallback ? me.shouldLongPollCallback() : !isHidden())
102
100
  );
103
101
  };
104
102
 
105
- var processMessages = function(messages) {
106
- var gotData = false;
107
- if ((!messages) || (messages.length === 0)) { return false; }
103
+ var processMessages = function (messages) {
104
+ if (!messages || messages.length === 0) {
105
+ return false;
106
+ }
108
107
 
109
108
  for (var i = 0; i < messages.length; i++) {
110
109
  var message = messages[i];
@@ -136,7 +135,7 @@
136
135
  return true;
137
136
  };
138
137
 
139
- var reqSuccess = function(messages) {
138
+ var reqSuccess = function (messages) {
140
139
  failCount = 0;
141
140
  if (paused) {
142
141
  if (messages) {
@@ -150,7 +149,7 @@
150
149
  return false;
151
150
  };
152
151
 
153
- var longPoller = function(poll, data) {
152
+ var longPoller = function (poll, data) {
154
153
  if (ajaxInProgress) {
155
154
  // never allow concurrent ajax reqs
156
155
  return;
@@ -183,7 +182,7 @@
183
182
 
184
183
  var dataType = chunked ? "text" : "json";
185
184
 
186
- var handle_progress = function(payload, position) {
185
+ var handle_progress = function (payload, position) {
187
186
  var separator = "\r\n|\r\n";
188
187
  var endChunk = payload.indexOf(separator, position);
189
188
 
@@ -206,31 +205,13 @@
206
205
  return handle_progress(payload, endChunk + separator.length);
207
206
  };
208
207
 
209
- var disableChunked = function() {
208
+ var disableChunked = function () {
210
209
  if (me.longPoll) {
211
210
  me.longPoll.abort();
212
211
  chunkedBackoff = 30;
213
212
  }
214
213
  };
215
214
 
216
- var setOnProgressListener = function(xhr) {
217
- var position = 0;
218
- // if it takes longer than 3000 ms to get first chunk, we have some proxy
219
- // this is messing with us, so just backoff from using chunked for now
220
- var chunkedTimeout = setTimeout(disableChunked, 3000);
221
- xhr.onprogress = function() {
222
- clearTimeout(chunkedTimeout);
223
- if (
224
- xhr.getResponseHeader("Content-Type") ===
225
- "application/json; charset=utf-8"
226
- ) {
227
- // not chunked we are sending json back
228
- chunked = false;
229
- return;
230
- }
231
- position = handle_progress(xhr.responseText, position);
232
- };
233
- };
234
215
  if (!me.ajax) {
235
216
  throw new Error("Either jQuery or the ajax adapter must be loaded");
236
217
  }
@@ -246,19 +227,18 @@
246
227
  "/poll" +
247
228
  (!longPoll ? "?dlp=t" : ""),
248
229
  data: data,
249
- cache: false,
250
230
  async: true,
251
231
  dataType: dataType,
252
232
  type: "POST",
253
233
  headers: headers,
254
234
  messageBus: {
255
235
  chunked: chunked,
256
- onProgressListener: function(xhr) {
236
+ onProgressListener: function (xhr) {
257
237
  var position = 0;
258
238
  // if it takes longer than 3000 ms to get first chunk, we have some proxy
259
239
  // this is messing with us, so just backoff from using chunked for now
260
240
  var chunkedTimeout = setTimeout(disableChunked, 3000);
261
- return (xhr.onprogress = function() {
241
+ return (xhr.onprogress = function () {
262
242
  clearTimeout(chunkedTimeout);
263
243
  if (
264
244
  xhr.getResponseHeader("Content-Type") ===
@@ -269,9 +249,9 @@
269
249
  position = handle_progress(xhr.responseText, position);
270
250
  }
271
251
  });
272
- }
252
+ },
273
253
  },
274
- xhr: function() {
254
+ xhr: function () {
275
255
  var xhr = jQuery.ajaxSettings.xhr();
276
256
  if (!chunked) {
277
257
  return xhr;
@@ -279,7 +259,7 @@
279
259
  this.messageBus.onProgressListener(xhr);
280
260
  return xhr;
281
261
  },
282
- success: function(messages) {
262
+ success: function (messages) {
283
263
  if (!chunked) {
284
264
  // we may have requested text so jQuery will not parse
285
265
  if (typeof messages === "string") {
@@ -288,7 +268,7 @@
288
268
  gotData = reqSuccess(messages);
289
269
  }
290
270
  },
291
- error: function(xhr, textStatus, err) {
271
+ error: function (xhr, textStatus) {
292
272
  if (xhr.status === 429) {
293
273
  var tryAfter =
294
274
  parseInt(
@@ -307,7 +287,7 @@
307
287
  totalAjaxFailures += 1;
308
288
  }
309
289
  },
310
- complete: function() {
290
+ complete: function () {
311
291
  ajaxInProgress = false;
312
292
 
313
293
  var interval;
@@ -345,14 +325,14 @@
345
325
  }
346
326
 
347
327
  if (started) {
348
- pollTimeout = setTimeout(function() {
328
+ pollTimeout = setTimeout(function () {
349
329
  pollTimeout = null;
350
330
  poll();
351
331
  }, interval);
352
332
  }
353
333
 
354
334
  me.longPoll = null;
355
- }
335
+ },
356
336
  });
357
337
 
358
338
  return req;
@@ -374,7 +354,7 @@
374
354
  baseUrl: baseUrl,
375
355
  headers: {},
376
356
  ajax: typeof jQuery !== "undefined" && jQuery.ajax,
377
- diagnostics: function() {
357
+ diagnostics: function () {
378
358
  console.log("Stopped: " + stopped + " Started: " + started);
379
359
  console.log("Current callbacks");
380
360
  console.log(callbacks);
@@ -391,42 +371,50 @@
391
371
  );
392
372
  },
393
373
 
394
- pause: function() {
374
+ pause: function () {
395
375
  paused = true;
396
376
  },
397
377
 
398
- resume: function() {
378
+ resume: function () {
399
379
  paused = false;
400
380
  processMessages(later);
401
381
  later = [];
402
382
  },
403
383
 
404
- stop: function() {
384
+ stop: function () {
405
385
  stopped = true;
406
386
  started = false;
407
387
  if (delayPollTimeout) {
408
388
  clearTimeout(delayPollTimeout);
409
389
  delayPollTimeout = null;
410
390
  }
391
+ if (pollTimeout) {
392
+ clearTimeout(pollTimeout);
393
+ pollTimeout = null;
394
+ }
411
395
  if (me.longPoll) {
412
396
  me.longPoll.abort();
413
397
  }
398
+ if (me.onVisibilityChange) {
399
+ document.removeEventListener("visibilitychange", me.onVisibilityChange);
400
+ me.onVisibilityChange = null;
401
+ }
414
402
  },
415
403
 
416
404
  // Start polling
417
- start: function() {
405
+ start: function () {
418
406
  if (started) return;
419
407
  started = true;
420
408
  stopped = false;
421
409
 
422
- var poll = function() {
410
+ var poll = function () {
423
411
  if (stopped) {
424
412
  return;
425
413
  }
426
414
 
427
415
  if (callbacks.length === 0 || hiddenTabShouldWait()) {
428
416
  if (!delayPollTimeout) {
429
- delayPollTimeout = setTimeout(function() {
417
+ delayPollTimeout = setTimeout(function () {
430
418
  delayPollTimeout = null;
431
419
  poll();
432
420
  }, parseInt(500 + Math.random() * 500));
@@ -448,25 +436,28 @@
448
436
 
449
437
  // monitor visibility, issue a new long poll when the page shows
450
438
  if (document.addEventListener && "hidden" in document) {
451
- me.visibilityEvent = document.addEventListener(
452
- "visibilitychange",
453
- function() {
454
- if (!document.hidden && !me.longPoll && pollTimeout) {
455
- clearTimeout(pollTimeout);
456
- clearTimeout(delayPollTimeout);
457
-
458
- delayPollTimeout = null;
459
- pollTimeout = null;
460
- poll();
461
- }
439
+ me.onVisibilityChange = function () {
440
+ if (
441
+ !document.hidden &&
442
+ !me.longPoll &&
443
+ (pollTimeout || delayPollTimeout)
444
+ ) {
445
+ clearTimeout(pollTimeout);
446
+ clearTimeout(delayPollTimeout);
447
+
448
+ delayPollTimeout = null;
449
+ pollTimeout = null;
450
+ poll();
462
451
  }
463
- );
452
+ };
453
+
454
+ document.addEventListener("visibilitychange", me.onVisibilityChange);
464
455
  }
465
456
 
466
457
  poll();
467
458
  },
468
459
 
469
- status: function() {
460
+ status: function () {
470
461
  if (paused) {
471
462
  return "paused";
472
463
  } else if (started) {
@@ -484,13 +475,18 @@
484
475
  // -1 will subscribe to all new messages
485
476
  // -2 will recieve last message + all new messages
486
477
  // -3 will recieve last 2 messages + all new messages
487
- subscribe: function(channel, func, lastId) {
478
+ // if undefined will default to -1
479
+ subscribe: function (channel, func, lastId) {
488
480
  if (!started && !stopped) {
489
481
  me.start();
490
482
  }
491
483
 
492
- if (typeof lastId !== "number") {
484
+ if (lastId === null || typeof lastId === "undefined") {
493
485
  lastId = -1;
486
+ } else if (typeof lastId !== "number") {
487
+ throw (
488
+ "lastId has type " + typeof lastId + " but a number was expected."
489
+ );
494
490
  }
495
491
 
496
492
  if (typeof channel !== "string") {
@@ -500,7 +496,7 @@
500
496
  callbacks.push({
501
497
  channel: channel,
502
498
  func: func,
503
- last_id: lastId
499
+ last_id: lastId,
504
500
  });
505
501
  if (me.longPoll) {
506
502
  me.longPoll.abort();
@@ -510,7 +506,7 @@
510
506
  },
511
507
 
512
508
  // Unsubscribe from a channel
513
- unsubscribe: function(channel, func) {
509
+ unsubscribe: function (channel, func) {
514
510
  // TODO allow for globbing in the middle of a channel name
515
511
  // like /something/*/something
516
512
  // at the moment we only support globbing /something/*
@@ -547,7 +543,7 @@
547
543
  }
548
544
 
549
545
  return removed;
550
- }
546
+ },
551
547
  };
552
548
  return me;
553
- }));
549
+ });