faye 0.3.1 → 0.3.2
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 faye might be problematic. Click here for more details.
- data/History.txt +12 -0
- data/Manifest.txt +5 -0
- data/README.txt +25 -7
- data/Rakefile +1 -0
- data/build/faye-client-min.js +1 -1
- data/build/faye.js +367 -186
- data/examples/node/faye-client-min.js +1 -1
- data/examples/node/faye.js +367 -186
- data/examples/shared/public/index.html +1 -0
- data/examples/shared/public/mootools.js +4329 -0
- data/examples/shared/public/prototype.js +4874 -0
- data/jake.yml +3 -1
- data/lib/faye-client-min.js +1 -1
- data/lib/faye.rb +5 -2
- data/lib/faye/channel.rb +4 -0
- data/lib/faye/client.rb +53 -29
- data/lib/faye/connection.rb +16 -37
- data/lib/faye/rack_adapter.rb +33 -28
- data/lib/faye/server.rb +24 -23
- data/lib/faye/timeouts.rb +21 -0
- data/lib/faye/transport.rb +17 -4
- data/test/scenario.js +73 -37
- data/test/scenario.rb +104 -0
- data/test/test_channel.rb +7 -1
- data/test/test_clients.js +28 -10
- data/test/test_clients.rb +154 -0
- data/test/test_grammar.rb +1 -1
- data/test/test_server.rb +8 -7
- metadata +17 -11
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Faye
         | 
| 2 | 
            +
              module Timeouts
         | 
| 3 | 
            +
                def add_timeout(name, delay, &block)
         | 
| 4 | 
            +
                  @timeouts ||= {}
         | 
| 5 | 
            +
                  return if @timeouts.has_key?(name)
         | 
| 6 | 
            +
                  @timeouts[name] = EventMachine.add_timer(delay) do
         | 
| 7 | 
            +
                    @timeouts.delete(name)
         | 
| 8 | 
            +
                    block.call
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                def remove_timeout(name)
         | 
| 13 | 
            +
                  @timeouts ||= {}
         | 
| 14 | 
            +
                  timeout = @timeouts[name]
         | 
| 15 | 
            +
                  return if timeout.nil?
         | 
| 16 | 
            +
                  EventMachine.cancel_timer(timeout)
         | 
| 17 | 
            +
                  @timeouts.delete(name)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
             | 
    
        data/lib/faye/transport.rb
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            require 'em-http'
         | 
| 2 2 | 
             
            require 'json'
         | 
| 3 | 
            +
            require 'uri'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module Faye
         | 
| 5 6 |  | 
| @@ -20,10 +21,11 @@ module Faye | |
| 20 21 |  | 
| 21 22 | 
             
                  request(message) { |responses|
         | 
| 22 23 | 
             
                    if block_given?
         | 
| 24 | 
            +
                      messages, deliverable = [], true
         | 
| 23 25 | 
             
                      [responses].flatten.each do |response|
         | 
| 24 26 |  | 
| 25 27 | 
             
                        if message.is_a?(Hash) and response['id'] == message['id']
         | 
| 26 | 
            -
                          block.call(response) 
         | 
| 28 | 
            +
                          deliverable = false if block.call(response) == false
         | 
| 27 29 | 
             
                        end
         | 
| 28 30 |  | 
| 29 31 | 
             
                        if response['advice']
         | 
| @@ -31,10 +33,12 @@ module Faye | |
| 31 33 | 
             
                        end
         | 
| 32 34 |  | 
| 33 35 | 
             
                        if response['data'] and response['channel']
         | 
| 34 | 
            -
                           | 
| 36 | 
            +
                          messages << response
         | 
| 35 37 | 
             
                        end
         | 
| 36 38 |  | 
| 37 39 | 
             
                      end
         | 
| 40 | 
            +
                      
         | 
| 41 | 
            +
                      @client.deliver_messages(messages) if deliverable
         | 
| 38 42 | 
             
                    end
         | 
| 39 43 | 
             
                  }
         | 
| 40 44 | 
             
                end
         | 
| @@ -77,8 +81,17 @@ module Faye | |
| 77 81 | 
             
                end
         | 
| 78 82 |  | 
| 79 83 | 
             
                def request(message, &block)
         | 
| 80 | 
            -
                   | 
| 81 | 
            -
                   | 
| 84 | 
            +
                  content = JSON.unparse(message)
         | 
| 85 | 
            +
                  params = {
         | 
| 86 | 
            +
                    :head => {
         | 
| 87 | 
            +
                      'Content-Type'    => 'application/json',
         | 
| 88 | 
            +
                      'host'            => URI.parse(@endpoint).host,
         | 
| 89 | 
            +
                      'Content-Length'  => content.length
         | 
| 90 | 
            +
                    },
         | 
| 91 | 
            +
                    :body    => content,
         | 
| 92 | 
            +
                    :timeout => -1
         | 
| 93 | 
            +
                  }
         | 
| 94 | 
            +
                  request = EventMachine::HttpRequest.new(@endpoint).post(params)
         | 
| 82 95 | 
             
                  request.callback do
         | 
| 83 96 | 
             
                    block.call(JSON.parse(request.response))
         | 
| 84 97 | 
             
                  end
         | 
    
        data/test/scenario.js
    CHANGED
    
    | @@ -4,42 +4,46 @@ var sys    = require('sys'), | |
| 4 4 | 
             
                assert = require('assert'),
         | 
| 5 5 | 
             
                faye   = require('../build/faye');
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 7 | 
            +
            Faye.Logging.logLevel = 'info';
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            AsyncScenario = Faye.Class({
         | 
| 10 | 
            +
              initialize: function(name) {
         | 
| 9 11 | 
             
                this._name    = name;
         | 
| 10 | 
            -
                this._block   = block;
         | 
| 11 12 | 
             
                this._clients = {};
         | 
| 12 13 | 
             
                this._inbox   = {};
         | 
| 13 14 | 
             
                this._pool    = 0;
         | 
| 14 15 | 
             
              },
         | 
| 15 16 |  | 
| 16 | 
            -
               | 
| 17 | 
            -
                 | 
| 18 | 
            -
                sys.puts('----------------------------------------------------------------');
         | 
| 19 | 
            -
                this._block.call(this);
         | 
| 17 | 
            +
              wait: function(time, Continue) {
         | 
| 18 | 
            +
                setTimeout(Continue, 1000 * time);
         | 
| 20 19 | 
             
              },
         | 
| 21 20 |  | 
| 22 | 
            -
              server: function(port) {
         | 
| 23 | 
            -
                sys.puts('Starting server on port ' + port);
         | 
| 21 | 
            +
              server: function(port, Continue) {
         | 
| 24 22 | 
             
                this._endpoint = 'http://0.0.0.0:' + port + '/comet';
         | 
| 25 23 | 
             
                var comet = this._comet  = new faye.NodeAdapter({mount: '/comet', timeout: 30});
         | 
| 26 24 | 
             
                this._server = http.createServer(function(request, response) {
         | 
| 27 25 | 
             
                  comet.call(request, response);
         | 
| 28 26 | 
             
                });
         | 
| 29 27 | 
             
                this._server.listen(port);
         | 
| 28 | 
            +
                Continue();
         | 
| 29 | 
            +
              },
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
              killServer: function(Continue) {
         | 
| 32 | 
            +
                if (this._server) this._server.close();
         | 
| 33 | 
            +
                this._server = undefined;
         | 
| 34 | 
            +
                setTimeout(Continue, 500);
         | 
| 30 35 | 
             
              },
         | 
| 31 36 |  | 
| 32 | 
            -
              httpClient: function(name, channels) {
         | 
| 33 | 
            -
                this._setupClient(new faye.Client(this._endpoint), name, channels);
         | 
| 37 | 
            +
              httpClient: function(name, channels, Continue) {
         | 
| 38 | 
            +
                this._setupClient(new faye.Client(this._endpoint, {timeout: 5}), name, channels, Continue);
         | 
| 34 39 | 
             
              },
         | 
| 35 40 |  | 
| 36 | 
            -
              localClient: function(name, channels) {
         | 
| 37 | 
            -
                this._setupClient(this._comet.getClient(), name, channels);
         | 
| 41 | 
            +
              localClient: function(name, channels, Continue) {
         | 
| 42 | 
            +
                this._setupClient(this._comet.getClient(), name, channels, Continue);
         | 
| 38 43 | 
             
              },
         | 
| 39 44 |  | 
| 40 | 
            -
              _setupClient: function(client, name, channels) {
         | 
| 45 | 
            +
              _setupClient: function(client, name, channels, Continue) {
         | 
| 41 46 | 
             
                Faye.each(channels, function(channel) {
         | 
| 42 | 
            -
                  sys.puts('Client ' + name + ' subscribing to ' + channel);
         | 
| 43 47 | 
             
                  client.subscribe(channel, function(message) {
         | 
| 44 48 | 
             
                    var box = this._inbox[name];
         | 
| 45 49 | 
             
                    box[channel] = box[channel] || [];
         | 
| @@ -49,35 +53,67 @@ Scenario = Faye.Class({ | |
| 49 53 | 
             
                this._clients[name] = client;
         | 
| 50 54 | 
             
                this._inbox[name]   = {};
         | 
| 51 55 | 
             
                this._pool         += 1;
         | 
| 56 | 
            +
                setTimeout(Continue, 500 * channels.length);
         | 
| 52 57 | 
             
              },
         | 
| 53 58 |  | 
| 54 | 
            -
               | 
| 55 | 
            -
                 | 
| 56 | 
            -
                setTimeout( | 
| 57 | 
            -
                  var displayMessage = JSON.stringify(message);
         | 
| 58 | 
            -
                  sys.puts('Client ' + from + ' publishing ' + displayMessage + ' to ' + channel);
         | 
| 59 | 
            -
                  self._clients[from].publish(channel, message);
         | 
| 60 | 
            -
                }, 500);
         | 
| 59 | 
            +
              publish: function(from, channel, message, Continue) {
         | 
| 60 | 
            +
                this._clients[from].publish(channel, message);
         | 
| 61 | 
            +
                setTimeout(Continue, 500);
         | 
| 61 62 | 
             
              },
         | 
| 62 63 |  | 
| 63 | 
            -
              checkInbox: function(expectedInbox) {
         | 
| 64 | 
            -
                 | 
| 65 | 
            -
                 | 
| 66 | 
            -
                  self._checkInbox(expectedInbox);
         | 
| 67 | 
            -
                  sys.puts('Shutting down server\n');
         | 
| 68 | 
            -
                  Faye.each(this._clients, function(name, client) { client.disconnect() });
         | 
| 69 | 
            -
                  self._server.close();
         | 
| 70 | 
            -
                  Scenario.runNext();
         | 
| 71 | 
            -
                }, 1000);
         | 
| 64 | 
            +
              checkInbox: function(expectedInbox, Continue) {
         | 
| 65 | 
            +
                assert.deepEqual(this._inbox, expectedInbox);
         | 
| 66 | 
            +
                Continue();
         | 
| 72 67 | 
             
              },
         | 
| 73 68 |  | 
| 74 | 
            -
               | 
| 75 | 
            -
                 | 
| 76 | 
            -
                 | 
| 69 | 
            +
              finish: function(Continue) {
         | 
| 70 | 
            +
                Faye.each(this._clients, function(name, client) { client.disconnect() });
         | 
| 71 | 
            +
                this._server.close();
         | 
| 72 | 
            +
                Continue();
         | 
| 73 | 
            +
              },
         | 
| 74 | 
            +
            });
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            SyncScenario = Faye.Class({
         | 
| 77 | 
            +
              initialize: function(name, block) {
         | 
| 78 | 
            +
                this._name = name;
         | 
| 79 | 
            +
                this._commands = [];
         | 
| 80 | 
            +
                this._scenario = new AsyncScenario();
         | 
| 81 | 
            +
                block.call(this);
         | 
| 82 | 
            +
              },
         | 
| 83 | 
            +
              
         | 
| 84 | 
            +
              run: function() {
         | 
| 85 | 
            +
                sys.puts('\n' + this._name);
         | 
| 86 | 
            +
                sys.puts('----------------------------------------------------------------');
         | 
| 87 | 
            +
                this._runNextCommand();
         | 
| 88 | 
            +
              },
         | 
| 89 | 
            +
              
         | 
| 90 | 
            +
              _runNextCommand: function() {
         | 
| 91 | 
            +
                if (this._commands.length === 0) return this._finish();
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                var command = this._commands.shift(),
         | 
| 94 | 
            +
                    method  = command[0],
         | 
| 95 | 
            +
                    args    = Array.prototype.slice.call(command[1]),
         | 
| 96 | 
            +
                    self = this;
         | 
| 97 | 
            +
                
         | 
| 98 | 
            +
                this._scenario[method].apply(this._scenario, args.concat(function() {
         | 
| 99 | 
            +
                  self._runNextCommand();
         | 
| 100 | 
            +
                }));
         | 
| 101 | 
            +
              },
         | 
| 102 | 
            +
              
         | 
| 103 | 
            +
              _finish: function() {
         | 
| 104 | 
            +
                sys.puts('No errors; Shutting down server\n');
         | 
| 105 | 
            +
                this._scenario.finish(function() { SyncScenario.runNext() });
         | 
| 106 | 
            +
              }
         | 
| 107 | 
            +
            });
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            ['wait', 'server', 'killServer', 'httpClient', 'localClient', 'publish', 'checkInbox'].
         | 
| 110 | 
            +
            forEach(function(method) {
         | 
| 111 | 
            +
              SyncScenario.prototype[method] = function() {
         | 
| 112 | 
            +
                this._commands.push([method, arguments]);
         | 
| 77 113 | 
             
              }
         | 
| 78 114 | 
             
            });
         | 
| 79 115 |  | 
| 80 | 
            -
            Faye.extend( | 
| 116 | 
            +
            Faye.extend(SyncScenario, {
         | 
| 81 117 | 
             
              _queue: [],
         | 
| 82 118 |  | 
| 83 119 | 
             
              enqueue: function(name, block) {
         | 
| @@ -93,7 +129,7 @@ Faye.extend(Scenario, { | |
| 93 129 | 
             
            });
         | 
| 94 130 |  | 
| 95 131 | 
             
            exports.run = function(name, block) {
         | 
| 96 | 
            -
               | 
| 97 | 
            -
              if (! | 
| 132 | 
            +
              SyncScenario.enqueue(name, block);
         | 
| 133 | 
            +
              if (!SyncScenario.running) SyncScenario.runNext();
         | 
| 98 134 | 
             
            };
         | 
| 99 135 |  | 
    
        data/test/scenario.rb
    ADDED
    
    | @@ -0,0 +1,104 @@ | |
| 1 | 
            +
            $VERBOSE = false
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Scenario
         | 
| 4 | 
            +
              module ClassMethods
         | 
| 5 | 
            +
                def scenario(name, &block)
         | 
| 6 | 
            +
                  define_method("test: #{name}", &block)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
              
         | 
| 10 | 
            +
              def self.included(klass)
         | 
| 11 | 
            +
                klass.extend ClassMethods
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              
         | 
| 14 | 
            +
              def setup
         | 
| 15 | 
            +
                Thread.new { EM.run }
         | 
| 16 | 
            +
                while not EM.reactor_running?; end
         | 
| 17 | 
            +
                @scenario = AsyncScenario.new
         | 
| 18 | 
            +
                @commands = []
         | 
| 19 | 
            +
                @started = false
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
              
         | 
| 22 | 
            +
              def teardown
         | 
| 23 | 
            +
                while EM.reactor_running?; end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
              
         | 
| 26 | 
            +
              def run(runner)
         | 
| 27 | 
            +
                @runner = runner
         | 
| 28 | 
            +
                super
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
              def method_missing(sym, *args)
         | 
| 32 | 
            +
                @commands << [sym, args]
         | 
| 33 | 
            +
                EM.next_tick { run_next_command unless @started }
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
              
         | 
| 36 | 
            +
              def run_next_command
         | 
| 37 | 
            +
                @started = true
         | 
| 38 | 
            +
                command = @commands.shift
         | 
| 39 | 
            +
                return EM.stop if command.nil?
         | 
| 40 | 
            +
                begin
         | 
| 41 | 
            +
                  @scenario.__send__(command.first, *command.last) do
         | 
| 42 | 
            +
                    run_next_command
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                rescue Object => e
         | 
| 45 | 
            +
                  @passed = false
         | 
| 46 | 
            +
                  add_failure(e.message, e.backtrace)
         | 
| 47 | 
            +
                  @runner.puke(self.class, self.name, e) if @runner.respond_to?(:puke)
         | 
| 48 | 
            +
                  block.call
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
              
         | 
| 52 | 
            +
              class AsyncScenario
         | 
| 53 | 
            +
                include Test::Unit::Assertions
         | 
| 54 | 
            +
                
         | 
| 55 | 
            +
                def initialize
         | 
| 56 | 
            +
                  @clients = {}
         | 
| 57 | 
            +
                  @inbox   = {}
         | 
| 58 | 
            +
                  @pool    = 0
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              
         | 
| 61 | 
            +
                def check_inbox(expected_inbox, &block)
         | 
| 62 | 
            +
                  assert_equal expected_inbox, @inbox
         | 
| 63 | 
            +
                  block.call
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
                
         | 
| 66 | 
            +
                def server(port, &block)
         | 
| 67 | 
            +
                  @endpoint = "http://0.0.0.0:#{port}/comet"
         | 
| 68 | 
            +
                  @comet = Faye::RackAdapter.new(:mount => '/comet', :timeout => 30)
         | 
| 69 | 
            +
                  Rack::Handler.get('thin').run(@comet, :Port => port) do |server|
         | 
| 70 | 
            +
                    @server = server
         | 
| 71 | 
            +
                    EM.next_tick(&block)
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
                
         | 
| 75 | 
            +
                def http_client(name, channels, &block)
         | 
| 76 | 
            +
                  setup_client(Faye::Client.new(@endpoint), name, channels, &block)
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
                
         | 
| 79 | 
            +
                def local_client(name, channels, &block)
         | 
| 80 | 
            +
                  setup_client(@comet.get_client, name, channels, &block)
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
                
         | 
| 83 | 
            +
                def setup_client(client, name, channels, &block)
         | 
| 84 | 
            +
                  channels.each do |channel|
         | 
| 85 | 
            +
                    client.subscribe(channel) do |message|
         | 
| 86 | 
            +
                      box = @inbox[name]
         | 
| 87 | 
            +
                      box[channel] ||= []
         | 
| 88 | 
            +
                      box[channel] << message
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                  @clients[name] = client
         | 
| 92 | 
            +
                  @inbox[name]   = {}
         | 
| 93 | 
            +
                  @pool         += 1
         | 
| 94 | 
            +
                  EM.add_timer(0.5 * channels.size, &block)
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
                
         | 
| 97 | 
            +
                def publish(from, channel, message, &block)
         | 
| 98 | 
            +
                  @clients[from].publish(channel, message)
         | 
| 99 | 
            +
                  EM.add_timer(2, &block)
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
              
         | 
| 103 | 
            +
            end
         | 
| 104 | 
            +
             | 
    
        data/test/test_channel.rb
    CHANGED
    
    | @@ -1,5 +1,5 @@ | |
| 1 1 | 
             
            require "test/unit"
         | 
| 2 | 
            -
            require "faye"
         | 
| 2 | 
            +
            require File.dirname(__FILE__) + "/../lib/faye"
         | 
| 3 3 |  | 
| 4 4 | 
             
            class TestChannel < Test::Unit::TestCase
         | 
| 5 5 | 
             
              include Faye
         | 
| @@ -15,6 +15,12 @@ class TestChannel < Test::Unit::TestCase | |
| 15 15 | 
             
                assert_equal 3,   tree['/va()$$lid/name']
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 | 
            +
              def test_keys
         | 
| 19 | 
            +
                tree = Channel::Tree.new
         | 
| 20 | 
            +
                tree['/foo'] = tree['/bar'] = tree['/foo/bar'] = true
         | 
| 21 | 
            +
                assert_equal %w[/bar /foo /foo/bar], tree.keys.sort
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
              
         | 
| 18 24 | 
             
              def test_globbing
         | 
| 19 25 | 
             
                tree = Channel::Tree.new
         | 
| 20 26 | 
             
                tree['/foo/bar']     = 1
         | 
    
        data/test/test_clients.js
    CHANGED
    
    | @@ -20,12 +20,30 @@ var Scenario = require('./scenario'), | |
| 20 20 | 
             
              assert.deepEqual(tree.glob('/channels/**').sort(), ['A','B','C']);
         | 
| 21 21 | 
             
            })();
         | 
| 22 22 |  | 
| 23 | 
            +
            Scenario.run("Server goes away, subscriptions should be revived",
         | 
| 24 | 
            +
            function() { with(this) {
         | 
| 25 | 
            +
              server(8000);
         | 
| 26 | 
            +
              httpClient('A', ['/channels/a']);
         | 
| 27 | 
            +
              httpClient('B', []);
         | 
| 28 | 
            +
              killServer();
         | 
| 29 | 
            +
              wait(6);
         | 
| 30 | 
            +
              server(8000);
         | 
| 31 | 
            +
              wait(22);
         | 
| 32 | 
            +
              publish('B', '/channels/a', {hello: 'world'});
         | 
| 33 | 
            +
              checkInbox({
         | 
| 34 | 
            +
                  A: {
         | 
| 35 | 
            +
                    '/channels/a': [{hello: 'world'}]
         | 
| 36 | 
            +
                  },
         | 
| 37 | 
            +
                  B: {}
         | 
| 38 | 
            +
              });
         | 
| 39 | 
            +
            }});
         | 
| 40 | 
            +
             | 
| 23 41 | 
             
            Scenario.run("Two HTTP clients, no messages delivered",
         | 
| 24 42 | 
             
            function() { with(this) {
         | 
| 25 43 | 
             
              server(8000);
         | 
| 26 44 | 
             
              httpClient('A', ['/channels/a']);
         | 
| 27 45 | 
             
              httpClient('B', []);
         | 
| 28 | 
            -
               | 
| 46 | 
            +
              publish('B', '/channels/b', {hello: 'world'});
         | 
| 29 47 | 
             
              checkInbox({
         | 
| 30 48 | 
             
                  A: {},
         | 
| 31 49 | 
             
                  B: {}
         | 
| @@ -37,7 +55,7 @@ function() { with(this) { | |
| 37 55 | 
             
              server(8000);
         | 
| 38 56 | 
             
              httpClient('A', ['/channels/a']);
         | 
| 39 57 | 
             
              httpClient('B', []);
         | 
| 40 | 
            -
               | 
| 58 | 
            +
              publish('B', '/channels/a', {hello: 'world'});
         | 
| 41 59 | 
             
              checkInbox({
         | 
| 42 60 | 
             
                  A: {
         | 
| 43 61 | 
             
                    '/channels/a': [{hello: 'world'}]
         | 
| @@ -51,7 +69,7 @@ function() { with(this) { | |
| 51 69 | 
             
              server(8000);
         | 
| 52 70 | 
             
              httpClient('A', ['/channels/a', '/channels/*']);
         | 
| 53 71 | 
             
              httpClient('B', []);
         | 
| 54 | 
            -
               | 
| 72 | 
            +
              publish('B', '/channels/a', {hello: 'world'});
         | 
| 55 73 | 
             
              checkInbox({
         | 
| 56 74 | 
             
                  A: {
         | 
| 57 75 | 
             
                    '/channels/a': [{hello: 'world'}],
         | 
| @@ -67,7 +85,7 @@ function() { with(this) { | |
| 67 85 | 
             
              httpClient('A', ['/channels/a']);
         | 
| 68 86 | 
             
              httpClient('B', []);
         | 
| 69 87 | 
             
              httpClient('C', ['/channels/c']);
         | 
| 70 | 
            -
               | 
| 88 | 
            +
              publish('B', '/channels/a', {chunky: 'bacon'});
         | 
| 71 89 | 
             
              checkInbox({
         | 
| 72 90 | 
             
                  A: {
         | 
| 73 91 | 
             
                    '/channels/a': [{chunky: 'bacon'}]
         | 
| @@ -83,7 +101,7 @@ function() { with(this) { | |
| 83 101 | 
             
              httpClient('A', ['/channels/shared']);
         | 
| 84 102 | 
             
              httpClient('B', []);
         | 
| 85 103 | 
             
              httpClient('C', ['/channels/shared']);
         | 
| 86 | 
            -
               | 
| 104 | 
            +
              publish('B', '/channels/shared', {chunky: 'bacon'});
         | 
| 87 105 | 
             
              checkInbox({
         | 
| 88 106 | 
             
                  A: {
         | 
| 89 107 | 
             
                    '/channels/shared': [{chunky: 'bacon'}]
         | 
| @@ -100,7 +118,7 @@ function() { with(this) { | |
| 100 118 | 
             
              server(8000);
         | 
| 101 119 | 
             
              httpClient('A', ['/channels/*']);
         | 
| 102 120 | 
             
              httpClient('B', []);
         | 
| 103 | 
            -
               | 
| 121 | 
            +
              publish('B', '/channels/anything', {msg: 'hey'});
         | 
| 104 122 | 
             
              checkInbox({
         | 
| 105 123 | 
             
                  A: {
         | 
| 106 124 | 
             
                    '/channels/*': [{msg: 'hey'}]
         | 
| @@ -114,7 +132,7 @@ function() { with(this) { | |
| 114 132 | 
             
              server(8000);
         | 
| 115 133 | 
             
              httpClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
         | 
| 116 134 | 
             
              httpClient('B', []);
         | 
| 117 | 
            -
               | 
| 135 | 
            +
              publish('B', '/channels/*', {msg: 'hey'});
         | 
| 118 136 | 
             
              checkInbox({
         | 
| 119 137 | 
             
                  A: {
         | 
| 120 138 | 
             
                    '/channels/name': [{msg: 'hey'}],
         | 
| @@ -129,7 +147,7 @@ function() { with(this) { | |
| 129 147 | 
             
              server(8000);
         | 
| 130 148 | 
             
              httpClient('A', ['/channels/*']);
         | 
| 131 149 | 
             
              httpClient('B', []);
         | 
| 132 | 
            -
               | 
| 150 | 
            +
              publish('B', '/channels/*', {msg: 'hey'});
         | 
| 133 151 | 
             
              checkInbox({
         | 
| 134 152 | 
             
                  A: {
         | 
| 135 153 | 
             
                    '/channels/*': [{msg: 'hey'}]
         | 
| @@ -143,7 +161,7 @@ function() { with(this) { | |
| 143 161 | 
             
              server(8000);
         | 
| 144 162 | 
             
              localClient('A', ['/channels/name', '/channels/hello', '/channels/nested/hello']);
         | 
| 145 163 | 
             
              localClient('B', []);
         | 
| 146 | 
            -
               | 
| 164 | 
            +
              publish('B', '/channels/**', {msg: 'hey'});
         | 
| 147 165 | 
             
              checkInbox({
         | 
| 148 166 | 
             
                  A: {
         | 
| 149 167 | 
             
                    '/channels/name': [{msg: 'hey'}],
         | 
| @@ -160,7 +178,7 @@ function() { with(this) { | |
| 160 178 | 
             
              localClient('A', ['/channels/hello', '/channels/nested/hello']);
         | 
| 161 179 | 
             
              localClient('B', []);
         | 
| 162 180 | 
             
              httpClient('C', ['/channels/name', '/channels/foo/**']);
         | 
| 163 | 
            -
               | 
| 181 | 
            +
              publish('B', '/channels/**', {msg: 'hey'});
         | 
| 164 182 | 
             
              checkInbox({
         | 
| 165 183 | 
             
                  A: {
         | 
| 166 184 | 
             
                    '/channels/hello': [{msg: 'hey'}],
         |