message_bus 3.3.7 → 3.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16601ac02268a477a94bf0347922162f66ce17dfdba1c4c655d576b46e78a79c
4
- data.tar.gz: 746e2295f873d9cfe75c28b079b406bb42329a992146d414c63292c2148a8f48
3
+ metadata.gz: b45c0d65f5a82c9465f620059d2125ebca09ee5619a9094fcbc309a0e179a983
4
+ data.tar.gz: b691c40ae2360ae661aa83f83e9647cb0223a70a6ca536f285db21ce791a3ae3
5
5
  SHA512:
6
- metadata.gz: 3d048dde14c63d5abdda283571cb9c2be1b7fc809a94c7f0b4ec2eaa4785da03bc47535264741f65cfa1d66c75e1b0169ea0de39449456b9c791ba91518d62e9
7
- data.tar.gz: 0cfbed1cab954e33805c94c7662c9094276615d5f338168da48d26f033da0f42685024b87aaee64d221e192d0592cd655fba7473da8d973616b218ce2858b045
6
+ metadata.gz: 2bb5db140d0fc8bbee5df6e00f57560b1e399c7cac86d825f9841c4c9d8bc805ec8f9ab2ea8ba0ad066c53039a44b5044ecef7299a74f942b3a52cd426ecbef6
7
+ data.tar.gz: 66fdc2a3c483d1b775b3963b71bdc422edcdf16233cb47aa2ec99d3a6946e6e0f6f6ca917db12e5d528de6dfb39cc494923e2abe380ca0c75d64d92f6f167d7e
@@ -1,16 +1,10 @@
1
- name: Message Bus Tests
1
+ name: CI
2
2
 
3
3
  on:
4
- pull_request:
5
4
  push:
6
5
  branches:
7
6
  - main
8
-
9
- env:
10
- PGHOST: localhost
11
- PGPORT: 5432
12
- PGPASSWORD: postgres
13
- PGUSER: postgres
7
+ pull_request:
14
8
 
15
9
  jobs:
16
10
  build:
@@ -18,10 +12,22 @@ jobs:
18
12
  name: Ruby ${{ matrix.ruby }} (redis ${{ matrix.redis }})
19
13
  timeout-minutes: 10
20
14
 
15
+ env:
16
+ PGHOST: localhost
17
+ PGPASSWORD: postgres
18
+ PGUSER: postgres
19
+
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ ruby: [2.6, 2.7, 3.0]
24
+ redis: [5, 6]
25
+
21
26
  services:
22
27
  postgres:
23
- image: postgres:14.0
28
+ image: postgres:14
24
29
  env:
30
+ POSTGRES_DB: message_bus_test
25
31
  POSTGRES_PASSWORD: postgres
26
32
  ports:
27
33
  - 5432:5432
@@ -36,19 +42,13 @@ jobs:
36
42
  --health-timeout 5s
37
43
  --health-retries 5
38
44
 
39
- strategy:
40
- fail-fast: false
41
- matrix:
42
- ruby: ["3.0", "2.7", "2.6"]
43
- redis: ["5", "6"]
44
-
45
45
  steps:
46
46
  - uses: actions/checkout@v2
47
47
 
48
48
  - uses: ruby/setup-ruby@v1
49
49
  with:
50
50
  ruby-version: ${{ matrix.ruby }}
51
- bundler-cache: true # 'bundle install' and cache
51
+ bundler-cache: true
52
52
 
53
53
  - name: Set up Node.js
54
54
  uses: actions/setup-node@v2
@@ -59,13 +59,41 @@ jobs:
59
59
  - name: Setup npm
60
60
  run: npm install
61
61
 
62
- - name: Create Database
63
- run: |
64
- createdb message_bus_test
65
-
66
62
  - name: Tests
67
63
  run: bundle exec rake
68
64
  timeout-minutes: 3
69
65
 
70
66
  - name: Linting
71
67
  run: npx eslint .
68
+
69
+ publish:
70
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
71
+ needs: build
72
+ runs-on: ubuntu-latest
73
+
74
+ steps:
75
+ - uses: actions/checkout@v2
76
+
77
+ - name: Release gem
78
+ uses: discourse/publish-rubygems-action@v2
79
+ id: publish-gem
80
+ env:
81
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
82
+ GIT_EMAIL: team@discourse.org
83
+ GIT_NAME: discoursebot
84
+
85
+ - name: Update package version
86
+ if: steps.publish-gem.outputs.new_version == 'true'
87
+ run: |
88
+ VERSION=$(ruby -r './lib/message_bus/version' -e 'puts MessageBus::VERSION')
89
+ sed -i "s/0.0.0-version-placeholder/$VERSION/" package.json
90
+ git config --global user.email "ci@ci.invalid"
91
+ git config --global user.name "Discourse CI"
92
+ git add package.json
93
+ git commit -m 'bump'
94
+
95
+ - name: Publish package
96
+ uses: JS-DevTools/npm-publish@v1
97
+ if: steps.publish-gem.outputs.new_version == 'true'
98
+ with:
99
+ token: ${{ secrets.NPM_TOKEN }}
data/.prettierrc ADDED
@@ -0,0 +1 @@
1
+ {}
@@ -84,6 +84,11 @@ module MessageBus
84
84
  raise ConcreteClassMustImplementError
85
85
  end
86
86
 
87
+ # Closes all open connections to the storage.
88
+ def destroy
89
+ raise ConcreteClassMustImplementError
90
+ end
91
+
87
92
  # Deletes all backlogs and their data. Does not delete non-backlog data that message_bus may persist, depending on the concrete backend implementation. Use with extreme caution.
88
93
  # @abstract
89
94
  def expire_all_backlogs!
@@ -212,6 +212,12 @@ module MessageBus
212
212
  client.reset!
213
213
  end
214
214
 
215
+ # No-op; this backend doesn't maintain any storage connections.
216
+ # (see Base#destroy)
217
+ def destroy
218
+ nil
219
+ end
220
+
215
221
  # (see Base#expire_all_backlogs!)
216
222
  def expire_all_backlogs!
217
223
  client.expire_all_backlogs!
@@ -86,10 +86,12 @@ module MessageBus
86
86
  hold { |conn| exec_prepared(conn, 'get_message', [channel, id]) { |r| r.getvalue(0, 0) } }
87
87
  end
88
88
 
89
- def reconnect
89
+ def after_fork
90
90
  sync do
91
- @listening_on.clear
91
+ @pid = Process.pid
92
+ INHERITED_CONNECTIONS.concat(@available)
92
93
  @available.clear
94
+ @listening_on.clear
93
95
  end
94
96
  end
95
97
 
@@ -101,6 +103,13 @@ module MessageBus
101
103
  end
102
104
  end
103
105
 
106
+ def destroy
107
+ sync do
108
+ @available.each(&:close)
109
+ @available.clear
110
+ end
111
+ end
112
+
104
113
  # use with extreme care, will nuke all of the data
105
114
  def expire_all_backlogs!
106
115
  reset!
@@ -174,11 +183,7 @@ module MessageBus
174
183
  def hold
175
184
  current_pid = Process.pid
176
185
  if current_pid != @pid
177
- @pid = current_pid
178
- sync do
179
- INHERITED_CONNECTIONS.concat(@available)
180
- @available.clear
181
- end
186
+ after_fork
182
187
  end
183
188
 
184
189
  if conn = sync { @allocated[Thread.current] }
@@ -253,12 +258,13 @@ module MessageBus
253
258
  # after 7 days inactive backlogs will be removed
254
259
  @max_backlog_age = 604800
255
260
  @clear_every = config[:clear_every] || 1
261
+ @mutex = Mutex.new
256
262
  end
257
263
 
258
264
  # Reconnects to Postgres; used after a process fork, typically triggered by a forking webserver
259
265
  # @see Base#after_fork
260
266
  def after_fork
261
- client.reconnect
267
+ client.after_fork
262
268
  end
263
269
 
264
270
  # (see Base#reset!)
@@ -266,6 +272,11 @@ module MessageBus
266
272
  client.reset!
267
273
  end
268
274
 
275
+ # (see Base#destroy)
276
+ def destroy
277
+ client.destroy
278
+ end
279
+
269
280
  # (see Base#expire_all_backlogs!)
270
281
  def expire_all_backlogs!
271
282
  client.expire_all_backlogs!
@@ -401,11 +412,7 @@ module MessageBus
401
412
  private
402
413
 
403
414
  def client
404
- @client ||= new_connection
405
- end
406
-
407
- def new_connection
408
- Client.new(@config)
415
+ @client || @mutex.synchronize { @client ||= Client.new(@config) }
409
416
  end
410
417
 
411
418
  def postgresql_channel_name
@@ -65,7 +65,7 @@ module MessageBus
65
65
  # Reconnects to Redis; used after a process fork, typically triggered by a forking webserver
66
66
  # @see Base#after_fork
67
67
  def after_fork
68
- pub_redis.disconnect!
68
+ @pub_redis&.disconnect!
69
69
  end
70
70
 
71
71
  # (see Base#reset!)
@@ -75,6 +75,11 @@ module MessageBus
75
75
  end
76
76
  end
77
77
 
78
+ # (see Base#destroy)
79
+ def destroy
80
+ @pub_redis&.disconnect!
81
+ end
82
+
78
83
  # Deletes all backlogs and their data. Does not delete ID pointers, so new publications will get IDs that continue from the last publication before the expiry. Use with extreme caution.
79
84
  # @see Base#expire_all_backlogs!
80
85
  def expire_all_backlogs!
@@ -46,6 +46,9 @@ class MessageBus::Client
46
46
  @bus = opts[:message_bus] || MessageBus
47
47
  @subscriptions = {}
48
48
  @chunks_sent = 0
49
+ @async_response = nil
50
+ @io = nil
51
+ @wrote_headers = false
49
52
  end
50
53
 
51
54
  # @yield executed with a lock on the Client instance
@@ -22,6 +22,7 @@ module MessageBus
22
22
  @lock = Mutex.new
23
23
  @message_bus = message_bus || MessageBus
24
24
  @publish_queue_in_memory = publish_queue_in_memory
25
+ @app_version = nil
25
26
  end
26
27
 
27
28
  def subscribers
@@ -9,6 +9,7 @@ module Thin
9
9
 
10
10
  def initialize
11
11
  @queue = []
12
+ @body_callback = nil
12
13
  end
13
14
 
14
15
  def call(body)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MessageBus
4
- VERSION = "3.3.7"
4
+ VERSION = "3.3.8"
5
5
  end
data/lib/message_bus.rb CHANGED
@@ -41,6 +41,10 @@ module MessageBus::Implementation
41
41
  def initialize
42
42
  @config = {}
43
43
  @mutex = Synchronizer.new
44
+ @off = false
45
+ @destroyed = false
46
+ @timer_thread = nil
47
+ @subscriber_thread = nil
44
48
  end
45
49
 
46
50
  # @param [Boolean] val whether or not to cache static assets for the diagnostics pages
@@ -533,6 +537,7 @@ module MessageBus::Implementation
533
537
  return if @destroyed
534
538
 
535
539
  reliable_pub_sub.global_unsubscribe
540
+ reliable_pub_sub.destroy
536
541
 
537
542
  @mutex.synchronize do
538
543
  return if @destroyed
data/message_bus.gemspec CHANGED
@@ -9,7 +9,8 @@ Gem::Specification.new do |gem|
9
9
  gem.summary = %q{}
10
10
  gem.homepage = "https://github.com/discourse/message_bus"
11
11
  gem.license = "MIT"
12
- gem.files = `git ls-files`.split($\)
12
+ gem.files = `git ls-files`.split($\) +
13
+ ["vendor/assets/javascripts/message-bus.js", "vendor/assets/javascripts/message-bus-ajax.js"]
13
14
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
15
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
16
  gem.name = "message_bus"
@@ -19,9 +20,11 @@ Gem::Specification.new do |gem|
19
20
 
20
21
  gem.add_runtime_dependency 'rack', '>= 1.1.3'
21
22
 
23
+ # Optional runtime dependencies
22
24
  gem.add_development_dependency 'redis'
23
25
  gem.add_development_dependency 'pg'
24
26
  gem.add_development_dependency 'concurrent-ruby' # for distributed-cache
27
+
25
28
  gem.add_development_dependency 'minitest'
26
29
  gem.add_development_dependency 'minitest-hooks'
27
30
  gem.add_development_dependency 'minitest-global_expectations'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "message-bus-client",
3
- "version": "3.3.5",
3
+ "version": "0.0.0-version-placeholder",
4
4
  "description": "A message bus client in Javascript",
5
5
  "main": "assets/message-bus.js",
6
6
  "keywords": [
@@ -12,10 +12,7 @@
12
12
  ],
13
13
  "jsnext:main": "assets/message-bus.js",
14
14
  "module": "assets/message-bus.js",
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/discourse/message_bus.git"
18
- },
15
+ "repository": "https://github.com/discourse/message_bus",
19
16
  "author": "Sam Saffron, Robin Ward",
20
17
  "license": "MIT",
21
18
  "bugs": {
data/spec/helpers.rb CHANGED
@@ -25,7 +25,12 @@ def test_config_for_backend(backend)
25
25
  when :redis
26
26
  config[:url] = ENV['REDISURL']
27
27
  when :postgres
28
- config[:backend_options] = { host: ENV['PGHOST'], user: ENV['PGUSER'] || ENV['USER'], password: ENV['PGPASSWORD'], dbname: ENV['PGDATABASE'] || 'message_bus_test' }
28
+ config[:backend_options] = {
29
+ host: ENV['PGHOST'],
30
+ user: ENV['PGUSER'] || ENV['USER'],
31
+ password: ENV['PGPASSWORD'],
32
+ dbname: ENV['PGDATABASE'] || 'message_bus_test'
33
+ }
29
34
  end
30
35
  config
31
36
  end
@@ -7,6 +7,7 @@ class FakeAsyncMiddleware
7
7
  @simulate_thin_async = false
8
8
  @simulate_hijack = false
9
9
  @in_async = false
10
+ @allow_chunked = false
10
11
  end
11
12
 
12
13
  def app
@@ -10,6 +10,7 @@ describe PUB_SUB_CLASS do
10
10
 
11
11
  after do
12
12
  @bus.reset!
13
+ @bus.destroy
13
14
  end
14
15
 
15
16
  describe "API parity" do
@@ -16,6 +16,7 @@ describe MessageBus::Client do
16
16
  end
17
17
 
18
18
  after do
19
+ @client.close
19
20
  @bus.reset!
20
21
  @bus.destroy
21
22
  end
@@ -6,6 +6,10 @@ require 'message_bus'
6
6
  class FakeAsync
7
7
  attr_accessor :cleanup_timer
8
8
 
9
+ def initialize
10
+ @sent = nil
11
+ end
12
+
9
13
  def <<(val)
10
14
  sleep 0.01 # simulate IO
11
15
  @sent ||= +""
@@ -18,13 +18,15 @@ describe PUB_SUB_CLASS do
18
18
 
19
19
  def work_it
20
20
  bus = new_bus
21
- $stdout.reopen("/dev/null", "w")
22
- $stderr.reopen("/dev/null", "w")
23
- # subscribe blocks, so we need a new bus to transmit
24
- new_bus.subscribe("/echo", 0) do |msg|
25
- bus.publish("/response", "#{msg.data}-#{Process.pid.to_s}")
21
+ bus.subscribe("/echo", 0) do |msg|
22
+ if msg.data == "done"
23
+ bus.global_unsubscribe
24
+ else
25
+ bus.publish("/response", "#{msg.data}-#{Process.pid.to_s}")
26
+ end
26
27
  end
27
28
  ensure
29
+ bus.destroy
28
30
  exit!(0)
29
31
  end
30
32
 
@@ -44,12 +46,14 @@ describe PUB_SUB_CLASS do
44
46
  test_never :memory
45
47
  skip("previous error") if self.class.error?
46
48
  GC.start
47
- new_bus.reset!
49
+ bus = new_bus
50
+ bus.reset!
51
+
48
52
  begin
49
53
  pids = (1..10).map { spawn_child }
50
54
  expected_responses = pids.map { |x| (0...10).map { |i| "0#{i}-#{x}" } }.flatten
51
55
  unexpected_responses = []
52
- bus = new_bus
56
+
53
57
  t = Thread.new do
54
58
  bus.subscribe("/response", 0) do |msg|
55
59
  if expected_responses.include?(msg.data)
@@ -59,10 +63,14 @@ describe PUB_SUB_CLASS do
59
63
  end
60
64
  end
61
65
  end
66
+
62
67
  10.times { |i| bus.publish("/echo", "0#{i}") }
63
- wait_for 4000 do
68
+
69
+ wait_for(2000) do
64
70
  expected_responses.empty?
65
71
  end
72
+
73
+ bus.publish("/echo", "done")
66
74
  bus.global_unsubscribe
67
75
  t.join
68
76
 
@@ -81,7 +89,9 @@ describe PUB_SUB_CLASS do
81
89
  Process.wait(pid)
82
90
  end
83
91
  end
92
+
84
93
  bus.global_unsubscribe
94
+ bus.destroy
85
95
  end
86
96
  end
87
97
  end
@@ -83,7 +83,7 @@ describe MessageBus::Rack::Middleware do
83
83
  { "FOO" => "BAR" }
84
84
  end
85
85
 
86
- Thread.new do
86
+ t = Thread.new do
87
87
  wait_for(2000) { middleware.in_async? }
88
88
  bus.publish "/foo", "םוֹלשָׁ"
89
89
  end
@@ -96,6 +96,7 @@ describe MessageBus::Rack::Middleware do
96
96
  parsed[0]["data"].must_equal "םוֹלשָׁ"
97
97
 
98
98
  last_response.headers["FOO"].must_equal "BAR"
99
+ t.join
99
100
  end
100
101
 
101
102
  it "should timeout within its alloted slot" do
@@ -23,7 +23,8 @@ describe MessageBus do
23
23
  @bus.off?.must_equal true
24
24
  end
25
25
 
26
- it "can call destroy twice" do
26
+ it "can call destroy multiple times" do
27
+ @bus.destroy
27
28
  @bus.destroy
28
29
  @bus.destroy
29
30
  end
@@ -0,0 +1,38 @@
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) {
6
+ 'use strict';
7
+ if (!global.MessageBus){
8
+ throw new Error("MessageBus must be loaded before the ajax adapter");
9
+ }
10
+
11
+ global.MessageBus.ajax = function(options){
12
+ var XHRImpl = (global.MessageBus && global.MessageBus.xhrImplementation) || global.XMLHttpRequest;
13
+ var xhr = new XHRImpl();
14
+ xhr.dataType = options.dataType;
15
+ xhr.open('POST', options.url);
16
+ for (var name in options.headers){
17
+ xhr.setRequestHeader(name, options.headers[name]);
18
+ }
19
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
20
+ if (options.messageBus.chunked){
21
+ options.messageBus.onProgressListener(xhr);
22
+ }
23
+ xhr.onreadystatechange = function(){
24
+ if (xhr.readyState === 4){
25
+ var status = xhr.status;
26
+ if (status >= 200 && status < 300 || status === 304){
27
+ options.success(xhr.responseText);
28
+ } else {
29
+ options.error(xhr, xhr.statusText);
30
+ }
31
+ options.complete();
32
+ }
33
+ }
34
+ xhr.send(new URLSearchParams(options.data).toString());
35
+ return xhr;
36
+ };
37
+
38
+ })(window);
@@ -0,0 +1,549 @@
1
+ /*global define, jQuery*/
2
+
3
+ (function (root, factory) {
4
+ if (typeof define === "function" && define.amd) {
5
+ // AMD. Register as an anonymous module.
6
+ define([], function () {
7
+ // Also create a global in case some scripts
8
+ // that are loaded still are looking for
9
+ // a global even when an AMD loader is in use.
10
+ return (root.MessageBus = factory());
11
+ });
12
+ } else {
13
+ // Browser globals
14
+ root.MessageBus = factory();
15
+ }
16
+ })(typeof self !== "undefined" ? self : this, function () {
17
+ "use strict";
18
+
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) {
22
+ var r = (Math.random() * 16) | 0;
23
+ var v = c === "x" ? r : (r & 0x3) | 0x8;
24
+ return v.toString(16);
25
+ });
26
+ };
27
+
28
+ var me;
29
+ var delayPollTimeout;
30
+ var ajaxInProgress = false;
31
+ var started = false;
32
+ var clientId = uniqueId();
33
+ var callbacks = [];
34
+ var failCount = 0;
35
+ var baseUrl = "/";
36
+ var paused = false;
37
+ var later = [];
38
+ var chunkedBackoff = 0;
39
+ var stopped;
40
+ var pollTimeout = null;
41
+ var totalAjaxFailures = 0;
42
+ var totalAjaxCalls = 0;
43
+ var lastAjax;
44
+
45
+ var isHidden = (function () {
46
+ var prefixes = ["", "webkit", "ms", "moz"];
47
+ var hiddenProperty;
48
+ for (var i = 0; i < prefixes.length; i++) {
49
+ var prefix = prefixes[i];
50
+ var check = prefix + (prefix === "" ? "hidden" : "Hidden");
51
+ if (document[check] !== undefined) {
52
+ hiddenProperty = check;
53
+ }
54
+ }
55
+
56
+ return function () {
57
+ if (hiddenProperty !== undefined) {
58
+ return document[hiddenProperty];
59
+ } else {
60
+ return !document.hasFocus;
61
+ }
62
+ };
63
+ })();
64
+
65
+ var hasLocalStorage = (function () {
66
+ try {
67
+ localStorage.setItem("mbTestLocalStorage", Date.now());
68
+ localStorage.removeItem("mbTestLocalStorage");
69
+ return true;
70
+ } catch (e) {
71
+ return false;
72
+ }
73
+ })();
74
+
75
+ var updateLastAjax = function () {
76
+ if (hasLocalStorage) {
77
+ localStorage.setItem("__mbLastAjax", Date.now());
78
+ }
79
+ };
80
+
81
+ var hiddenTabShouldWait = function () {
82
+ if (hasLocalStorage && isHidden()) {
83
+ var lastAjaxCall = parseInt(localStorage.getItem("__mbLastAjax"), 10);
84
+ var deltaAjax = Date.now() - lastAjaxCall;
85
+
86
+ return deltaAjax >= 0 && deltaAjax < me.minHiddenPollInterval;
87
+ }
88
+ return false;
89
+ };
90
+
91
+ var hasonprogress = new XMLHttpRequest().onprogress === null;
92
+ var allowChunked = function () {
93
+ return me.enableChunkedEncoding && hasonprogress;
94
+ };
95
+
96
+ var shouldLongPoll = function () {
97
+ return (
98
+ me.alwaysLongPoll ||
99
+ (me.shouldLongPollCallback ? me.shouldLongPollCallback() : !isHidden())
100
+ );
101
+ };
102
+
103
+ var processMessages = function (messages) {
104
+ if (!messages || messages.length === 0) {
105
+ return false;
106
+ }
107
+
108
+ for (var i = 0; i < messages.length; i++) {
109
+ var message = messages[i];
110
+ for (var j = 0; j < callbacks.length; j++) {
111
+ var callback = callbacks[j];
112
+ if (callback.channel === message.channel) {
113
+ callback.last_id = message.message_id;
114
+ try {
115
+ callback.func(message.data, message.global_id, message.message_id);
116
+ } catch (e) {
117
+ if (console.log) {
118
+ console.log(
119
+ "MESSAGE BUS FAIL: callback " +
120
+ callback.channel +
121
+ " caused exception " +
122
+ e.stack
123
+ );
124
+ }
125
+ }
126
+ }
127
+ if (message.channel === "/__status") {
128
+ if (message.data[callback.channel] !== undefined) {
129
+ callback.last_id = message.data[callback.channel];
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ return true;
136
+ };
137
+
138
+ var reqSuccess = function (messages) {
139
+ failCount = 0;
140
+ if (paused) {
141
+ if (messages) {
142
+ for (var i = 0; i < messages.length; i++) {
143
+ later.push(messages[i]);
144
+ }
145
+ }
146
+ } else {
147
+ return processMessages(messages);
148
+ }
149
+ return false;
150
+ };
151
+
152
+ var longPoller = function (poll, data) {
153
+ if (ajaxInProgress) {
154
+ // never allow concurrent ajax reqs
155
+ return;
156
+ }
157
+
158
+ var gotData = false;
159
+ var aborted = false;
160
+ var rateLimited = false;
161
+ var rateLimitedSeconds;
162
+
163
+ lastAjax = new Date();
164
+ totalAjaxCalls += 1;
165
+ data.__seq = totalAjaxCalls;
166
+
167
+ var longPoll = shouldLongPoll() && me.enableLongPolling;
168
+ var chunked = longPoll && allowChunked();
169
+ if (chunkedBackoff > 0) {
170
+ chunkedBackoff--;
171
+ chunked = false;
172
+ }
173
+
174
+ var headers = { "X-SILENCE-LOGGER": "true" };
175
+ for (var name in me.headers) {
176
+ headers[name] = me.headers[name];
177
+ }
178
+
179
+ if (!chunked) {
180
+ headers["Dont-Chunk"] = "true";
181
+ }
182
+
183
+ var dataType = chunked ? "text" : "json";
184
+
185
+ var handle_progress = function (payload, position) {
186
+ var separator = "\r\n|\r\n";
187
+ var endChunk = payload.indexOf(separator, position);
188
+
189
+ if (endChunk === -1) {
190
+ return position;
191
+ }
192
+
193
+ var chunk = payload.substring(position, endChunk);
194
+ chunk = chunk.replace(/\r\n\|\|\r\n/g, separator);
195
+
196
+ try {
197
+ reqSuccess(JSON.parse(chunk));
198
+ } catch (e) {
199
+ if (console.log) {
200
+ console.log("FAILED TO PARSE CHUNKED REPLY");
201
+ console.log(data);
202
+ }
203
+ }
204
+
205
+ return handle_progress(payload, endChunk + separator.length);
206
+ };
207
+
208
+ var disableChunked = function () {
209
+ if (me.longPoll) {
210
+ me.longPoll.abort();
211
+ chunkedBackoff = 30;
212
+ }
213
+ };
214
+
215
+ if (!me.ajax) {
216
+ throw new Error("Either jQuery or the ajax adapter must be loaded");
217
+ }
218
+
219
+ updateLastAjax();
220
+
221
+ ajaxInProgress = true;
222
+ var req = me.ajax({
223
+ url:
224
+ me.baseUrl +
225
+ "message-bus/" +
226
+ me.clientId +
227
+ "/poll" +
228
+ (!longPoll ? "?dlp=t" : ""),
229
+ data: data,
230
+ async: true,
231
+ dataType: dataType,
232
+ type: "POST",
233
+ headers: headers,
234
+ messageBus: {
235
+ chunked: chunked,
236
+ onProgressListener: function (xhr) {
237
+ var position = 0;
238
+ // if it takes longer than 3000 ms to get first chunk, we have some proxy
239
+ // this is messing with us, so just backoff from using chunked for now
240
+ var chunkedTimeout = setTimeout(disableChunked, 3000);
241
+ return (xhr.onprogress = function () {
242
+ clearTimeout(chunkedTimeout);
243
+ if (
244
+ xhr.getResponseHeader("Content-Type") ===
245
+ "application/json; charset=utf-8"
246
+ ) {
247
+ chunked = false; // not chunked, we are sending json back
248
+ } else {
249
+ position = handle_progress(xhr.responseText, position);
250
+ }
251
+ });
252
+ },
253
+ },
254
+ xhr: function () {
255
+ var xhr = jQuery.ajaxSettings.xhr();
256
+ if (!chunked) {
257
+ return xhr;
258
+ }
259
+ this.messageBus.onProgressListener(xhr);
260
+ return xhr;
261
+ },
262
+ success: function (messages) {
263
+ if (!chunked) {
264
+ // we may have requested text so jQuery will not parse
265
+ if (typeof messages === "string") {
266
+ messages = JSON.parse(messages);
267
+ }
268
+ gotData = reqSuccess(messages);
269
+ }
270
+ },
271
+ error: function (xhr, textStatus) {
272
+ if (xhr.status === 429) {
273
+ var tryAfter =
274
+ parseInt(
275
+ xhr.getResponseHeader && xhr.getResponseHeader("Retry-After")
276
+ ) || 0;
277
+ tryAfter = tryAfter || 0;
278
+ if (tryAfter < 15) {
279
+ tryAfter = 15;
280
+ }
281
+ rateLimitedSeconds = tryAfter;
282
+ rateLimited = true;
283
+ } else if (textStatus === "abort") {
284
+ aborted = true;
285
+ } else {
286
+ failCount += 1;
287
+ totalAjaxFailures += 1;
288
+ }
289
+ },
290
+ complete: function () {
291
+ ajaxInProgress = false;
292
+
293
+ var interval;
294
+ try {
295
+ if (rateLimited) {
296
+ interval = Math.max(me.minPollInterval, rateLimitedSeconds * 1000);
297
+ } else if (gotData || aborted) {
298
+ interval = me.minPollInterval;
299
+ } else {
300
+ interval = me.callbackInterval;
301
+ if (failCount > 2) {
302
+ interval = interval * failCount;
303
+ } else if (!shouldLongPoll()) {
304
+ interval = me.backgroundCallbackInterval;
305
+ }
306
+ if (interval > me.maxPollInterval) {
307
+ interval = me.maxPollInterval;
308
+ }
309
+
310
+ interval -= new Date() - lastAjax;
311
+
312
+ if (interval < 100) {
313
+ interval = 100;
314
+ }
315
+ }
316
+ } catch (e) {
317
+ if (console.log && e.message) {
318
+ console.log("MESSAGE BUS FAIL: " + e.message);
319
+ }
320
+ }
321
+
322
+ if (pollTimeout) {
323
+ clearTimeout(pollTimeout);
324
+ pollTimeout = null;
325
+ }
326
+
327
+ if (started) {
328
+ pollTimeout = setTimeout(function () {
329
+ pollTimeout = null;
330
+ poll();
331
+ }, interval);
332
+ }
333
+
334
+ me.longPoll = null;
335
+ },
336
+ });
337
+
338
+ return req;
339
+ };
340
+
341
+ me = {
342
+ /* shared between all tabs */
343
+ minHiddenPollInterval: 1500,
344
+ enableChunkedEncoding: true,
345
+ enableLongPolling: true,
346
+ callbackInterval: 15000,
347
+ backgroundCallbackInterval: 60000,
348
+ minPollInterval: 100,
349
+ maxPollInterval: 3 * 60 * 1000,
350
+ callbacks: callbacks,
351
+ clientId: clientId,
352
+ alwaysLongPoll: false,
353
+ shouldLongPollCallback: undefined,
354
+ baseUrl: baseUrl,
355
+ headers: {},
356
+ ajax: typeof jQuery !== "undefined" && jQuery.ajax,
357
+ diagnostics: function () {
358
+ console.log("Stopped: " + stopped + " Started: " + started);
359
+ console.log("Current callbacks");
360
+ console.log(callbacks);
361
+ console.log(
362
+ "Total ajax calls: " +
363
+ totalAjaxCalls +
364
+ " Recent failure count: " +
365
+ failCount +
366
+ " Total failures: " +
367
+ totalAjaxFailures
368
+ );
369
+ console.log(
370
+ "Last ajax call: " + (new Date() - lastAjax) / 1000 + " seconds ago"
371
+ );
372
+ },
373
+
374
+ pause: function () {
375
+ paused = true;
376
+ },
377
+
378
+ resume: function () {
379
+ paused = false;
380
+ processMessages(later);
381
+ later = [];
382
+ },
383
+
384
+ stop: function () {
385
+ stopped = true;
386
+ started = false;
387
+ if (delayPollTimeout) {
388
+ clearTimeout(delayPollTimeout);
389
+ delayPollTimeout = null;
390
+ }
391
+ if (pollTimeout) {
392
+ clearTimeout(pollTimeout);
393
+ pollTimeout = null;
394
+ }
395
+ if (me.longPoll) {
396
+ me.longPoll.abort();
397
+ }
398
+ if (me.onVisibilityChange) {
399
+ document.removeEventListener("visibilitychange", me.onVisibilityChange);
400
+ me.onVisibilityChange = null;
401
+ }
402
+ },
403
+
404
+ // Start polling
405
+ start: function () {
406
+ if (started) return;
407
+ started = true;
408
+ stopped = false;
409
+
410
+ var poll = function () {
411
+ if (stopped) {
412
+ return;
413
+ }
414
+
415
+ if (callbacks.length === 0 || hiddenTabShouldWait()) {
416
+ if (!delayPollTimeout) {
417
+ delayPollTimeout = setTimeout(function () {
418
+ delayPollTimeout = null;
419
+ poll();
420
+ }, parseInt(500 + Math.random() * 500));
421
+ }
422
+ return;
423
+ }
424
+
425
+ var data = {};
426
+ for (var i = 0; i < callbacks.length; i++) {
427
+ data[callbacks[i].channel] = callbacks[i].last_id;
428
+ }
429
+
430
+ // could possibly already be started
431
+ // notice the delay timeout above
432
+ if (!me.longPoll) {
433
+ me.longPoll = longPoller(poll, data);
434
+ }
435
+ };
436
+
437
+ // monitor visibility, issue a new long poll when the page shows
438
+ if (document.addEventListener && "hidden" in document) {
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();
451
+ }
452
+ };
453
+
454
+ document.addEventListener("visibilitychange", me.onVisibilityChange);
455
+ }
456
+
457
+ poll();
458
+ },
459
+
460
+ status: function () {
461
+ if (paused) {
462
+ return "paused";
463
+ } else if (started) {
464
+ return "started";
465
+ } else if (stopped) {
466
+ return "stopped";
467
+ } else {
468
+ throw "Cannot determine current status";
469
+ }
470
+ },
471
+
472
+ // Subscribe to a channel
473
+ // if lastId is 0 or larger, it will recieve messages AFTER that id
474
+ // if lastId is negative it will perform lookbehind
475
+ // -1 will subscribe to all new messages
476
+ // -2 will recieve last message + all new messages
477
+ // -3 will recieve last 2 messages + all new messages
478
+ // if undefined will default to -1
479
+ subscribe: function (channel, func, lastId) {
480
+ if (!started && !stopped) {
481
+ me.start();
482
+ }
483
+
484
+ if (lastId === null || typeof lastId === "undefined") {
485
+ lastId = -1;
486
+ } else if (typeof lastId !== "number") {
487
+ throw (
488
+ "lastId has type " + typeof lastId + " but a number was expected."
489
+ );
490
+ }
491
+
492
+ if (typeof channel !== "string") {
493
+ throw "Channel name must be a string!";
494
+ }
495
+
496
+ callbacks.push({
497
+ channel: channel,
498
+ func: func,
499
+ last_id: lastId,
500
+ });
501
+ if (me.longPoll) {
502
+ me.longPoll.abort();
503
+ }
504
+
505
+ return func;
506
+ },
507
+
508
+ // Unsubscribe from a channel
509
+ unsubscribe: function (channel, func) {
510
+ // TODO allow for globbing in the middle of a channel name
511
+ // like /something/*/something
512
+ // at the moment we only support globbing /something/*
513
+ var glob = false;
514
+ if (channel.indexOf("*", channel.length - 1) !== -1) {
515
+ channel = channel.substr(0, channel.length - 1);
516
+ glob = true;
517
+ }
518
+
519
+ var removed = false;
520
+
521
+ for (var i = callbacks.length - 1; i >= 0; i--) {
522
+ var callback = callbacks[i];
523
+ var keep;
524
+
525
+ if (glob) {
526
+ keep = callback.channel.substr(0, channel.length) !== channel;
527
+ } else {
528
+ keep = callback.channel !== channel;
529
+ }
530
+
531
+ if (!keep && func && callback.func !== func) {
532
+ keep = true;
533
+ }
534
+
535
+ if (!keep) {
536
+ callbacks.splice(i, 1);
537
+ removed = true;
538
+ }
539
+ }
540
+
541
+ if (removed && me.longPoll) {
542
+ me.longPoll.abort();
543
+ }
544
+
545
+ return removed;
546
+ },
547
+ };
548
+ return me;
549
+ });
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: 3.3.7
4
+ version: 3.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-17 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -272,6 +272,7 @@ files:
272
272
  - ".eslintrc.js"
273
273
  - ".github/workflows/ci.yml"
274
274
  - ".gitignore"
275
+ - ".prettierrc"
275
276
  - ".rubocop.yml"
276
277
  - CHANGELOG
277
278
  - DEV.md
@@ -355,6 +356,8 @@ files:
355
356
  - spec/spec_helper.rb
356
357
  - spec/support/jasmine-browser.json
357
358
  - vendor/assets/javascripts/.gitignore
359
+ - vendor/assets/javascripts/message-bus-ajax.js
360
+ - vendor/assets/javascripts/message-bus.js
358
361
  homepage: https://github.com/discourse/message_bus
359
362
  licenses:
360
363
  - MIT
@@ -374,7 +377,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
374
377
  - !ruby/object:Gem::Version
375
378
  version: '0'
376
379
  requirements: []
377
- rubygems_version: 3.2.33
380
+ rubygems_version: 3.1.6
378
381
  signing_key:
379
382
  specification_version: 4
380
383
  summary: ''