├── .gitignore
├── requirements.txt
├── demos
├── presence
│ ├── html
│ │ ├── img
│ │ │ ├── hand_thumbsup.png
│ │ │ ├── hand_thumbsdown.png
│ │ │ ├── glyphicons-halflings.png
│ │ │ └── glyphicons-halflings-white.png
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── js
│ │ │ ├── presence-0.1.0.js
│ │ │ ├── stomp-0.1.0.js
│ │ │ └── nullmq-0.1.0.js
│ │ └── css
│ │ │ └── bootstrap-responsive-2.0.1.css
│ ├── ruby
│ │ ├── lib
│ │ │ ├── clone.rb
│ │ │ └── clone
│ │ │ │ ├── client.rb
│ │ │ │ └── server.rb
│ │ ├── Gemfile
│ │ ├── Gemfile.lock
│ │ ├── bin
│ │ │ ├── redirector
│ │ │ ├── chat_server
│ │ │ ├── chat_client
│ │ │ ├── presence_client
│ │ │ └── presence_server
│ │ └── README.md
│ └── python
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── server.py
├── requirements.txt
├── bridge
│ ├── index.html
│ ├── test.html
│ ├── test_sockets.js
│ ├── server.py
│ └── bridge.py
└── reflector
│ ├── index.html
│ ├── test_pipeline.js
│ ├── test_pubsub.js
│ ├── test.html
│ ├── test_rr.js
│ └── server.py
├── package.json
├── TODO
├── README.md
├── LICENSE
├── test
├── websocket.mock.coffee
├── queue.spec.coffee
├── server.mock.coffee
└── nullmq.spec.coffee
├── Cakefile
├── dist
├── lib
│ ├── qunit.css
│ ├── stomp.js
│ └── qunit.js
└── nullmq.js
├── notes.txt
└── src
└── nullmq.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/test
2 | node_modules/
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | coffee-script
2 | jasmine-node
--------------------------------------------------------------------------------
/demos/presence/html/img/hand_thumbsup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrium/nullmq/HEAD/demos/presence/html/img/hand_thumbsup.png
--------------------------------------------------------------------------------
/demos/presence/html/img/hand_thumbsdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrium/nullmq/HEAD/demos/presence/html/img/hand_thumbsdown.png
--------------------------------------------------------------------------------
/demos/presence/ruby/lib/clone.rb:
--------------------------------------------------------------------------------
1 | module Clone
2 | autoload :Server, 'clone/server'
3 | autoload :Client, 'clone/client'
4 | end
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nullmq"
3 | , "version": "0.1.0"
4 | , "dependencies" : {
5 | "jasmine-node": "*"
6 | }
7 | }
--------------------------------------------------------------------------------
/demos/presence/html/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrium/nullmq/HEAD/demos/presence/html/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | -Multipart
2 | -DEALER, ROUTER
3 | -Bridge integration tests
4 | -Reconnect
5 | -Subscribe Filters (filter on sending socket)
6 | -high watermark
7 |
--------------------------------------------------------------------------------
/demos/presence/html/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/progrium/nullmq/HEAD/demos/presence/html/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/demos/presence/ruby/Gemfile:
--------------------------------------------------------------------------------
1 | lib = File.expand_path('../lib/', __FILE__)
2 | $:.unshift lib unless $:.include?(lib)
3 |
4 | source "http://rubygems.org"
5 |
6 | gem "ffi-rzmq"
7 |
--------------------------------------------------------------------------------
/demos/presence/python/README.md:
--------------------------------------------------------------------------------
1 | # Python presence and chat bridge
2 |
3 | ## Install
4 |
5 | `pip install -r demos/requirements.txt`
6 |
7 | ## Run
8 |
9 | `python server.py`
10 |
--------------------------------------------------------------------------------
/demos/presence/ruby/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | ffi (1.0.11)
5 | ffi-rzmq (0.9.3)
6 | ffi
7 |
8 | PLATFORMS
9 | ruby
10 |
11 | DEPENDENCIES
12 | ffi-rzmq
13 |
--------------------------------------------------------------------------------
/demos/presence/html/README.md:
--------------------------------------------------------------------------------
1 | # Presence html frontend
2 |
3 | ## Run
4 |
5 | `python -m SimpleHTTPServer 8080`
6 |
7 | ## Use
8 |
9 | * make sure to start python bridge and ruby chat and presence servers first
10 | * go to `http://localhost:8080/` in your browser
11 |
--------------------------------------------------------------------------------
/demos/requirements.txt:
--------------------------------------------------------------------------------
1 | git+git://github.com/progrium/stomp4py.git#egg=stomp4py
2 | git+git://github.com/progrium/WebSocket-for-Python#egg=ws4py
3 | git+git://github.com/progrium/gservice.git#egg=gservice
4 | gevent==0.13.3
5 | gevent_zeromq
6 | greenlet==0.3.1
7 | lockfile==0.9.1
8 | nose==1.1.2
9 | python-daemon==1.6
10 | setproctitle==1.1.2
11 | wsgiref==0.1.2
--------------------------------------------------------------------------------
/demos/presence/python/requirements.txt:
--------------------------------------------------------------------------------
1 | git+git://github.com/progrium/stomp4py.git#egg=stomp4py
2 | git+git://github.com/progrium/WebSocket-for-Python#egg=ws4py
3 | git+git://github.com/progrium/gservice.git#egg=gservice
4 | gevent==0.13.3
5 | gevent_zeromq
6 | greenlet==0.3.1
7 | lockfile==0.9.1
8 | nose==1.1.2
9 | python-daemon==1.6
10 | setproctitle==1.1.2
11 | wsgiref==0.1.2
--------------------------------------------------------------------------------
/demos/bridge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 | Run tests
15 |
16 |
--------------------------------------------------------------------------------
/demos/reflector/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 | Run tests
15 |
16 |
--------------------------------------------------------------------------------
/demos/presence/ruby/bin/redirector:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "rubygems"
4 | require "bundler/setup"
5 |
6 | require "socket"
7 |
8 | server = TCPServer.new(8888)
9 | private_ipv4 = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}.ip_address
10 | puts "starting redirector to #{private_ipv4}:8080"
11 | loop do
12 | client = server.accept
13 | headers = "HTTP/1.1 301 Moved\r\nDate: Tue, 14 Dec 2010 10:48:45 GMT\r\nServer: Ruby\r\nLocation: http://#{private_ipv4}:8080/\r\nContent-length: 0\r\n\r\n"
14 | client.puts headers
15 | client.close
16 | end
17 |
--------------------------------------------------------------------------------
/demos/presence/ruby/README.md:
--------------------------------------------------------------------------------
1 | # Presense and chat server in Ruby using 0mq
2 |
3 | ## Installation
4 |
5 | * clone this repository
6 | * `bundle install`
7 |
8 | ## Start presence server
9 |
10 | `./bin/presence_server`
11 |
12 | ## Start chat server
13 |
14 | `./bin/chat_server`
15 |
16 | ## Start presence client
17 |
18 | `./bin/presence_client `, e.g. `./bin/presence_client bulat "I'm online"`
19 |
20 | ## Start chat client
21 |
22 | `./bin/chat_client `, e.g. `./bin/chat_client bulat`
23 |
24 | ### Using chat client
25 |
26 | When client is connected to server, type any message and hit enter to see it displayed to peers
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NullMQ
2 |
3 | ZeroMQ semantics in the browser. For more information, check out [these
4 | slides](http://www.slideshare.net/progrium/nullmq-pdx).
5 |
6 | This is still very early.
7 |
8 | ## Installation
9 |
10 | * `git clone ...`
11 | * `cat requirements.txt | xargs npm install -g`
12 | * `cake build`
13 |
14 | ## Testing
15 |
16 | * `cake test`
17 |
18 | ## Demos
19 |
20 | Under demos there are two server implementations (bridge and reflector).
21 | There is also an example web application showing presence and chat using the
22 | clone pattern.
23 |
24 | * `pip install -r demos/requirements.txt`
25 |
26 | ## Contributors
27 |
28 | * Jeff Lindsay
29 | * Bulat Shakirzyanov
30 |
31 | ## License
32 |
33 | MIT
34 |
--------------------------------------------------------------------------------
/demos/presence/ruby/bin/chat_server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "rubygems"
4 | require "bundler/setup"
5 |
6 | require "ffi-rzmq"
7 | require "clone"
8 | require "json"
9 |
10 | @messages = []
11 |
12 | server = Clone::Server.new(ZMQ::Context.new(1), {
13 | :publish => "tcp://*:10004",
14 | :router => "tcp://*:10005",
15 | :pull => "tcp://*:10006"
16 | })
17 |
18 | server.on_request do |payload|
19 | JSON.generate(@messages.dup)
20 | end
21 |
22 | server.on_push do |payload|
23 | begin
24 | message = JSON.parse(payload)
25 | message['timestamp'] = Time.now
26 | @messages << message
27 | server.publish(JSON.generate(message))
28 | rescue JSON::ParserError
29 | end
30 | end
31 |
32 | begin
33 | puts "starting server...\r\n"
34 | server.start
35 | loop do
36 | # do nothing, since
37 | sleep()
38 | end
39 | rescue Interrupt
40 | puts "stopping server...\r\n"
41 | server.stop
42 | end
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2012 Jeff Lindsay
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/demos/reflector/test_pipeline.js:
--------------------------------------------------------------------------------
1 | var ctx1 = null;
2 | var ctx2 = null;
3 | var ctx3 = null;
4 |
5 | module("Pipelining", {
6 | setup: function() {
7 | ctx1 = new nullmq.Context(TEST.url);
8 | ctx2 = new nullmq.Context(TEST.url);
9 | ctx3 = new nullmq.Context(TEST.url);
10 | },
11 |
12 | teardown: function() {
13 | ctx1.term();
14 | ctx2.term();
15 | ctx3.term();
16 | }
17 | });
18 |
19 | test("basic push-pull", function() {
20 | var messages = [];
21 | var message = "Foobar";
22 |
23 | var pull = ctx1.socket(nullmq.PULL);
24 | pull.connect('/endpoint');
25 | pull.recvall(function(msg) {
26 | messages.push(msg);
27 | });
28 |
29 | var push = ctx2.socket(nullmq.PUSH);
30 | push.connect('/endpoint');
31 | push.send(message);
32 | push.send(message);
33 |
34 | var wait = setInterval(function() {
35 | if (messages.length > 1) {
36 | clearInterval(wait);
37 | start();
38 | equals(messages.length, 2);
39 | notEqual(messages.indexOf(message), -1);
40 | }
41 | }, 20);
42 |
43 | stop(TEST.timeout);
44 | });
--------------------------------------------------------------------------------
/test/websocket.mock.coffee:
--------------------------------------------------------------------------------
1 | class exports.WebSocketMock
2 | constructor: (@url) ->
3 | @onclose = ->
4 | @onopen = ->
5 | @onerror = ->
6 | @onmessage = ->
7 | @readyState = 0
8 | @bufferedAmount = 0
9 | @extensions = ''
10 | @protocol = ''
11 | setTimeout(@handle_open, 0)
12 |
13 | # WebSocket API
14 |
15 | close: ->
16 | @handle_close()
17 | @readyState = 2
18 |
19 | send: (msg) ->
20 | if @readyState isnt 1 then return false
21 | @handle_send(msg)
22 | return true
23 |
24 | # Helpers
25 |
26 | _accept: ->
27 | @readyState = 1
28 | @onopen({'type': 'open'})
29 |
30 | _shutdown: ->
31 | @readyState = 3
32 | @onclose({'type': 'close'})
33 |
34 | _error: ->
35 | @readyState = 3
36 | @onerror({'type': 'error'})
37 |
38 | _respond: (data) ->
39 | @onmessage({'type': 'message', 'data': data})
40 |
41 | # Handlers
42 |
43 | handle_send: (msg) ->
44 | # implement me
45 |
46 | handle_close: ->
47 | # implement me
48 |
49 | handle_open: ->
50 | # implement me
--------------------------------------------------------------------------------
/demos/presence/ruby/bin/chat_client:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "rubygems"
4 | require "bundler/setup"
5 |
6 | require "ffi-rzmq"
7 | require "clone"
8 | require "json"
9 |
10 | @name = ARGV[0]
11 |
12 | client = Clone::Client.new(ZMQ::Context.new(1), {
13 | :subscribe => "tcp://localhost:10004",
14 | :request => "tcp://localhost:10005",
15 | :push => "tcp://localhost:10006"
16 | })
17 |
18 | client.on_response do |payload|
19 | begin
20 | messages = JSON.parse(payload)
21 | messages.each do |msg|
22 | $stdout << sprintf("[%s] <%s> %s\r\n", msg['timestamp'], msg['name'], msg['text'])
23 | end
24 | rescue JSON::ParseError
25 | end
26 | end
27 |
28 | client.on_publish do |payload|
29 | begin
30 | msg = JSON.parse(payload)
31 | $stdout << sprintf("[%s] <%s> %s\r\n", msg['timestamp'], msg['name'], msg['text'])
32 | rescue JSON::ParseError
33 | end
34 | end
35 |
36 | begin
37 | $stdout << "connecting...\r\n"
38 | client.connect
39 | while msg = $stdin.gets.chomp
40 | $stdout << "\r"
41 | client.push(JSON.generate({
42 | "text" => msg,
43 | "name" => @name
44 | }))
45 | end
46 | rescue Interrupt
47 | $stdout << "disconnecting...\r\n"
48 | client.disconnect
49 | end
--------------------------------------------------------------------------------
/demos/presence/ruby/bin/presence_client:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "rubygems"
4 | require "bundler/setup"
5 |
6 | require "ffi-rzmq"
7 | require "clone"
8 | require "json"
9 |
10 | @peers = {}
11 | @name = ARGV[0]
12 | @text = ARGV[1]
13 |
14 | client = Clone::Client.new(ZMQ::Context.new(1), {
15 | :subscribe => "tcp://localhost:10001",
16 | :request => "tcp://localhost:10002",
17 | :push => "tcp://localhost:10003"
18 | })
19 |
20 | client.on_response do |payload|
21 | begin
22 | peers = JSON.parse(payload)
23 | peers.each do |name, peer|
24 | @peers[name] = peer
25 | end
26 | rescue JSON::ParserError
27 | end
28 | end
29 |
30 | client.on_publish do |payload|
31 | begin
32 | peer = JSON.parse(payload)
33 | @peers[peer['name']] ||= {}
34 | @peers[peer['name']].merge!(peer)
35 | rescue JSON::ParserError
36 | end
37 | end
38 |
39 | begin
40 | $stdout << "connecting...\r\n"
41 | client.connect
42 | loop do
43 | client.push(JSON.generate({
44 | "name" => @name,
45 | "text" => @text,
46 | "online" => true,
47 | "timeout" => 2
48 | }))
49 | sleep(1)
50 | end
51 | rescue Interrupt
52 | $stdout << "disconnecting...\r\n"
53 | client.disconnect
54 | end
55 |
--------------------------------------------------------------------------------
/demos/reflector/test_pubsub.js:
--------------------------------------------------------------------------------
1 | var ctx1 = null;
2 | var ctx2 = null;
3 | var ctx3 = null;
4 |
5 | module("Publish-Subscribe", {
6 | setup: function() {
7 | ctx1 = new nullmq.Context(TEST.url);
8 | ctx2 = new nullmq.Context(TEST.url);
9 | ctx3 = new nullmq.Context(TEST.url);
10 | },
11 |
12 | teardown: function() {
13 | ctx1.term();
14 | ctx2.term();
15 | ctx3.term();
16 | }
17 | });
18 |
19 | test("basic publish-subscribe", function() {
20 | var messages = [];
21 | var message = "Foobar";
22 |
23 | // Subscriber 1 on Context 1
24 | var sub1 = ctx1.socket(nullmq.SUB);
25 | sub1.connect('/publisher');
26 | sub1.recvall(function(msg) {
27 | messages.push(msg);
28 | });
29 |
30 | // Subscriber 2 on Context 1
31 | var sub2 = ctx1.socket(nullmq.SUB);
32 | sub2.connect('/publisher');
33 | sub2.recvall(function(msg) {
34 | messages.push(msg);
35 | });
36 |
37 | // Subscriber 3 on Context 2
38 | var sub3 = ctx2.socket(nullmq.SUB);
39 | sub3.connect('/publisher');
40 | sub3.recvall(function(msg) {
41 | messages.push(msg);
42 | });
43 |
44 | // Publisher on Context 3
45 | var pub = ctx3.socket(nullmq.PUB);
46 | pub.bind('/publisher');
47 | pub.send(message);
48 |
49 | var wait = setInterval(function() {
50 | if (messages.length > 2) {
51 | clearInterval(wait);
52 | start();
53 | equals(messages.length, 3);
54 | notEqual(messages.indexOf(message), -1);
55 | }
56 | }, 20);
57 |
58 | stop(TEST.timeout);
59 | });
--------------------------------------------------------------------------------
/test/queue.spec.coffee:
--------------------------------------------------------------------------------
1 | {Queue} = require('../nullmq.js')
2 |
3 | describe "Queue", ->
4 | it "gives you FIFO queue behavior", ->
5 | q = new Queue()
6 | item1 = "foobar"
7 | item2 = ["foobar"]
8 | item3 = {foo: "bar"}
9 | item4 = 42
10 | q.put(item1)
11 | q.put(item2)
12 | q.put(item3)
13 | q.put(item4)
14 | expect(q.get()).toBe item1
15 | expect(q.get()).toBe item2
16 | expect(q.get()).toBe item3
17 | expect(q.get()).toBe item4
18 |
19 | it "can have a maximum size", ->
20 | q = new Queue(3)
21 | items = ["foo", "bar", "baz", "qux"]
22 | expect(q.isFull()).toBeFalsy()
23 | expect(q.put(items[0])).toBe items[0]
24 | q.put(items[1])
25 | q.put(items[2])
26 | expect(q.isFull()).toBeTruthy()
27 | expect(q.put(items[3])).toBeUndefined()
28 | expect(q.getLength()).toEqual 3
29 |
30 | it "lets you peek without dequeuing", ->
31 | q = new Queue()
32 | q.put("foo")
33 | q.put("bar")
34 | q.put("baz")
35 | q.get() # Drop "foo"
36 | expect(q.peek()).toEqual "bar"
37 | expect(q.get()).toEqual "bar"
38 |
39 | it "lets you register a one-time watch callback for items", ->
40 | q = new Queue()
41 | from_watch = []
42 | watcher = ->
43 | from_watch.push q.get()
44 | q.watch watcher
45 | q.put("item1")
46 | q.put("item2")
47 | expect(q.getLength()).toEqual 1
48 | expect(from_watch.length).toEqual 1
49 | expect(from_watch).toContain "item1"
50 | q.watch watcher
51 | expect(from_watch.length).toEqual 2
52 | expect(from_watch).toContain "item2"
--------------------------------------------------------------------------------
/demos/presence/ruby/bin/presence_server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "rubygems"
4 | require "bundler/setup"
5 |
6 | require "ffi-rzmq"
7 | require "clone"
8 | require "json"
9 |
10 | @clients = {}
11 |
12 | server = Clone::Server.new(ZMQ::Context.new(1), {
13 | :publish => "tcp://*:10001",
14 | :router => "tcp://*:10002",
15 | :pull => "tcp://*:10003"
16 | })
17 |
18 | server.on_request do |payload|
19 | JSON.generate(Hash[*(@clients.dup.map do |k, v|
20 | [k, {"name" => k, "online" => v['online'], "text" => v['text']}]
21 | end.flatten)])
22 | end
23 |
24 | server.on_push do |payload|
25 | begin
26 | data = JSON.parse(payload)
27 | client = @clients[data["name"]] || {}
28 | client["last_seen"] = Time.now
29 | if client["online"] != data["online"]
30 | server.publish(JSON.generate({
31 | "name" => data["name"],
32 | "online" => data["online"]
33 | }))
34 | client["online"] = data["online"]
35 | end
36 | if client["text"] != data["text"]
37 | server.publish(JSON.generate({
38 | "name" => data["name"],
39 | "text" => data["text"]
40 | }))
41 | client["text"] = data["text"]
42 | end
43 | client["timeout"] = data["timeout"]
44 | @clients[data["name"]] = client unless @clients[data["name"]]
45 | rescue JSON::ParserError
46 | end
47 | end
48 |
49 | begin
50 | puts "starting server..."
51 | server.start
52 |
53 | loop do
54 | @clients.dup.each do |name, client|
55 | if client['online'] && ((Time.now - client['last_seen']) > client['timeout'])
56 | @clients[name]['online'] = false
57 | server.publish(JSON.generate({
58 | "name" => name,
59 | "online" => false
60 | }))
61 | end
62 | end
63 | end
64 | rescue Interrupt
65 | puts "stopping server..."
66 | server.stop
67 | end
68 |
--------------------------------------------------------------------------------
/demos/bridge/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | NullMQ Bridge
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
36 |
37 |
38 |
39 |
40 | Tests requires that a Bridge Server accepting Stomp WebSocket protocol is running with the configuration:
41 |
42 | - URL:
43 |
- User: /
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demos/reflector/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | NullMQ Reflector
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
38 |
39 |
40 |
41 |
42 | Tests requires that a Reflector Server accepting Stomp WebSocket protocol is running with the configuration:
43 |
44 | - URL:
45 |
- User: /
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/demos/presence/ruby/lib/clone/client.rb:
--------------------------------------------------------------------------------
1 | require 'thread'
2 |
3 | module Clone
4 | class Client
5 | def initialize(context, options, threads = Thread, push_queue = nil)
6 | @context = context
7 | @options = options
8 | @threads = threads
9 | @push_queue = push_queue || Queue.new
10 | end
11 |
12 | def connect
13 | @threads.abort_on_exception = true
14 | start_sub
15 | do_request
16 | start_push
17 | end
18 |
19 | def disconnect
20 | stop_sub
21 | stop_push
22 | @context.terminate
23 | end
24 |
25 | def on_response(&block)
26 | @request_handler = block
27 | end
28 |
29 | def on_publish(&block)
30 | @subscribe_handler = block
31 | end
32 |
33 | def push(msg)
34 | @push_queue << msg
35 | end
36 |
37 | private
38 |
39 | def start_sub
40 | @subscribed = false
41 | @sub = spawn_socket(@options[:subscribe], ZMQ::SUB) do |sock|
42 | @subscribed = sock.setsockopt(ZMQ::SUBSCRIBE, '') == 0 unless @subscribed
43 | sock.recv_string(change = '')
44 | @subscribe_handler.call(change)
45 | end
46 | end
47 |
48 | def stop_sub
49 | @sub[:stop] = true
50 | @threads.kill(@sub)
51 | end
52 |
53 | def do_request
54 | request = @context.socket(ZMQ::REQ)
55 | request.connect(@options[:request])
56 |
57 | request.send_string('')
58 | request.recv_string(data = '')
59 | request.close
60 |
61 | @request_handler.call(data)
62 | end
63 |
64 | def start_push
65 | @push = spawn_socket(@options[:push], ZMQ::PUSH) do |sock|
66 | sock.send_string(@push_queue.pop)
67 | end
68 | end
69 |
70 | def stop_push
71 | @push[:stop] = true
72 | @threads.kill(@push)
73 | end
74 |
75 | def spawn_socket(endpoint, socket_type)
76 | @threads.new(@context.socket(socket_type)) do |sock|
77 | thread = @threads.current
78 | thread[:stop] = false
79 |
80 | sock.connect(endpoint)
81 |
82 | until thread[:stop]
83 | yield sock
84 | end
85 |
86 | sock.close
87 | end
88 | end
89 | end
90 | end
--------------------------------------------------------------------------------
/demos/presence/ruby/lib/clone/server.rb:
--------------------------------------------------------------------------------
1 | require 'thread'
2 |
3 | module Clone
4 | class Server
5 | def initialize(context, options, threads = Thread, pub_queue = nil)
6 | @context = context
7 | @options = options
8 | @threads = threads
9 | @pub_queue = pub_queue || Queue.new
10 | end
11 |
12 | def start
13 | @threads.abort_on_exception = true
14 | start_pull
15 | start_pub
16 | start_router
17 | end
18 |
19 | def stop
20 | stop_pull
21 | stop_pub
22 | stop_router
23 | @context.terminate
24 | end
25 |
26 | def on_request(&block)
27 | @router_handler = block
28 | end
29 |
30 | def on_push(&block)
31 | @pull_handler = block
32 | end
33 |
34 | def publish(msg)
35 | @pub_queue << msg
36 | end
37 |
38 | private
39 |
40 | def start_pull
41 | @pull = spawn_socket(@options[:pull], ZMQ::PULL) do |sock|
42 | sock.recv_string(payload = '')
43 | @pull_handler.call(payload)
44 | end
45 | end
46 |
47 | def stop_pull
48 | @pull[:stop] = true
49 | @threads.kill(@pull)
50 | end
51 |
52 | def start_pub
53 | @pub = spawn_socket(@options[:publish], ZMQ::PUB) do |sock|
54 | sock.send_string(@pub_queue.pop)
55 | end
56 | end
57 |
58 | def stop_pub
59 | @pub[:stop] = true
60 | @threads.kill(@pub)
61 | end
62 |
63 | def start_router
64 | @router = spawn_socket(@options[:router], ZMQ::ROUTER) do |sock|
65 | sock.recv_string(address = '')
66 | sock.recv_string('') if sock.more_parts?
67 | sock.recv_string(data = '') if sock.more_parts?
68 | sock.send_string(address, ZMQ::SNDMORE)
69 | sock.send_string('', ZMQ::SNDMORE)
70 | sock.send_string(@router_handler.call(data))
71 | end
72 | end
73 |
74 | def stop_router
75 | @router[:stop] = true
76 | @threads.kill(@router)
77 | end
78 |
79 | def spawn_socket(endpoint, socket_type)
80 | @threads.new(@context.socket(socket_type)) do |sock|
81 | sock.bind(endpoint)
82 |
83 | thread = @threads.current
84 | thread[:stop] = false
85 |
86 | until thread[:stop]
87 | yield sock
88 | end
89 |
90 | sock.close
91 | end
92 | end
93 | end
94 | end
--------------------------------------------------------------------------------
/Cakefile:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | {exec} = require 'child_process'
3 | util = require 'util'
4 |
5 | task 'watch', 'Watch for changes in coffee files to build and test', ->
6 | util.log "Watching for changes in src and test"
7 | lastTest = 0
8 | watchDir 'src', ->
9 | invoke 'build:src'
10 | invoke 'build:test'
11 | watchDir 'test', ->
12 | invoke 'build:test'
13 | watchDir 'dist/test', (file)->
14 | # We only want to run tests once (a second),
15 | # even if a bunch of test files change
16 | time = new Date().getTime()
17 | if (time-lastTest) > 1000
18 | lastTest = time
19 | invoke 'test'
20 |
21 | task 'test', 'Run the tests', ->
22 | util.log "Running tests..."
23 | exec "jasmine-node --nocolor dist/test", (err, stdout, stderr) ->
24 | if err
25 | handleError(parseTestResults(stdout), stderr)
26 | else
27 | displayNotification "Tests pass!"
28 | util.log lastLine(stdout)
29 |
30 | task 'build', 'Build source and tests', ->
31 | invoke 'build:src'
32 | invoke 'build:test'
33 |
34 | task 'build:src', 'Build the src files into lib', ->
35 | util.log "Compiling src..."
36 | exec "coffee -o dist/ -c src/", (err, stdout, stderr) ->
37 | handleError(err) if err
38 |
39 | task 'build:test', 'Build the test files into lib/test', ->
40 | util.log "Compiling test..."
41 | exec "coffee -o dist/test/ -c test/", (err, stdout, stderr) ->
42 | handleError(err) if err
43 |
44 | watchDir = (dir, callback) ->
45 | fs.readdir dir, (err, files) ->
46 | handleError(err) if err
47 | for file in files then do (file) ->
48 | fs.watchFile "#{dir}/#{file}", (curr, prev) ->
49 | if +curr.mtime isnt +prev.mtime
50 | callback "#{dir}/#{file}"
51 |
52 | parseTestResults = (data) ->
53 | lines = (line for line in data.split('\n') when line.length > 5)
54 | results = lines.pop()
55 | details = lines[1...lines.length-2].join('\n')
56 | results + '\n\n' + details + '\n'
57 |
58 | lastLine = (data) ->
59 | (line for line in data.split('\n') when line.length > 5).pop()
60 |
61 | handleError = (error, stderr) ->
62 | if stderr? and !error
63 | util.log stderr
64 | displayNotification stderr.match(/\n(Error:[^\n]+)/)?[1]
65 | else
66 | util.log error
67 | displayNotification error
68 |
69 | displayNotification = (message = '') ->
70 | options = { title: 'CoffeeScript' }
71 | try require('growl').notify message, options
--------------------------------------------------------------------------------
/demos/reflector/test_rr.js:
--------------------------------------------------------------------------------
1 | var ctx1 = null;
2 | var ctx2 = null;
3 | var ctx3 = null;
4 |
5 | module("Request-Reply", {
6 | setup: function() {
7 | ctx1 = new nullmq.Context(TEST.url);
8 | ctx2 = new nullmq.Context(TEST.url);
9 | ctx3 = new nullmq.Context(TEST.url);
10 | },
11 |
12 | teardown: function() {
13 | ctx1.term();
14 | ctx2.term();
15 | ctx3.term();
16 | }
17 | });
18 |
19 | test("basic request-reply", function() {
20 | var message = "Foobar";
21 |
22 | var rep = ctx1.socket(nullmq.REP);
23 | rep.bind('/echo');
24 | rep.recvall(function(msg) {
25 | rep.send(msg);
26 | });
27 |
28 | var req = ctx2.socket(nullmq.REQ);
29 | req.connect('/echo');
30 | req.send(message);
31 | req.recv(function(msg) {
32 | start();
33 | equals(msg, message);
34 | })
35 |
36 | stop(TEST.timeout);
37 | });
38 |
39 | test("round robin across connected reply sockets", function() {
40 | var messages = [];
41 |
42 | var rep1 = ctx1.socket(nullmq.REP);
43 | rep1.bind('/reply1');
44 | rep1.recvall(function(msg) {
45 | rep1.send('reply1');
46 | });
47 |
48 | var rep2 = ctx1.socket(nullmq.REP);
49 | rep2.bind('/reply2');
50 | rep2.recvall(function(msg) {
51 | rep2.send('reply2');
52 | });
53 |
54 | var req = ctx2.socket(nullmq.REQ);
55 | req.connect('/reply1');
56 | req.connect('/reply2');
57 | req.send("First request");
58 | req.recv(function(msg) {
59 | start();
60 | messages.push(msg);
61 |
62 | req.send("Second request");
63 | req.recv(function(msg) {
64 | messages.push(msg);
65 |
66 | notEqual(messages.indexOf('reply1'), -1);
67 | notEqual(messages.indexOf('reply2'), -1);
68 | });
69 | })
70 |
71 | stop(TEST.timeout);
72 | });
73 |
74 | test("mixing connect and bind reply sockets in different contexts", function() {
75 | var messages = [];
76 |
77 | var rep1 = ctx1.socket(nullmq.REP);
78 | rep1.bind('/rep');
79 | rep1.recvall(function(msg) {
80 | rep1.send('replyA');
81 | });
82 |
83 | var rep2 = ctx2.socket(nullmq.REP);
84 | rep2.connect('/req');
85 | rep2.recvall(function(msg) {
86 | rep2.send('replyB');
87 | });
88 |
89 | var req = ctx3.socket(nullmq.REQ);
90 | req.connect('/rep');
91 | req.bind('/req');
92 | req.send("First request");
93 | req.recv(function(msg) {
94 | start();
95 | messages.push(msg);
96 |
97 | req.send("Second request");
98 | req.recv(function(msg) {
99 | messages.push(msg);
100 |
101 | notEqual(messages.indexOf('replyA'), -1);
102 | notEqual(messages.indexOf('replyB'), -1);
103 | });
104 | })
105 |
106 | stop(TEST.timeout);
107 | });
--------------------------------------------------------------------------------
/demos/bridge/test_sockets.js:
--------------------------------------------------------------------------------
1 | var ctx = null;
2 | var control = null;
3 |
4 | module("ZeroMQ test sockets", {
5 | setup: function() {
6 | ctx = new nullmq.Context(TEST.url);
7 | if (control == null) {
8 | control = new nullmq.Context(TEST.url).socket(nullmq.REQ);
9 | control.connect('/control');
10 | }
11 | control.send("setup");
12 | },
13 |
14 | teardown: function() {
15 | control.send("teardown");
16 | control.recv(function(rep) { });
17 | ctx.term();
18 | }
19 | });
20 |
21 | function whenReady(fn) {
22 | control.recv(function(ready) {
23 | if (ready == 'ok') {
24 | fn();
25 | } else {
26 | ok(false, "Remote setup failed");
27 | }
28 | });
29 | }
30 |
31 | test("publishing", function() {
32 | whenReady(function() {
33 | var message = "Foobar";
34 | var pub = ctx.socket(nullmq.PUB);
35 | pub.connect('/sub');
36 | pub.send(message);
37 | control.send('sub');
38 | control.recv(function(msg) {
39 | start();
40 | equals(msg, message);
41 | });
42 |
43 | });
44 | stop(TEST.timeout);
45 | });
46 |
47 | test("subscribing", function() {
48 | whenReady(function() {
49 | var sub = ctx.socket(nullmq.SUB);
50 | sub.connect('/pub');
51 | sub.recv(function(actual) {
52 | start();
53 | control.send('pub');
54 | control.recv(function(expected) {
55 | equals(actual, expected);
56 | });
57 | });
58 | });
59 | stop(TEST.timeout);
60 | });
61 |
62 | test("pushing", function() {
63 | whenReady(function() {
64 | var message = "Foobar";
65 | var push = ctx.socket(nullmq.PUSH);
66 | push.connect('/pull');
67 | push.send(message);
68 | control.send('push');
69 | control.recv(function(msg) {
70 | start();
71 | equals(msg, message);
72 | });
73 | });
74 | stop(TEST.timeout);
75 | });
76 |
77 | test("pulling", function() {
78 | whenReady(function() {
79 | var pull = ctx.socket(nullmq.PULL);
80 | pull.connect('/push');
81 | pull.recv(function(actual) {
82 | start();
83 | control.send('push');
84 | control.recv(function(expected) {
85 | equals(actual, expected);
86 | });
87 | });
88 | });
89 | stop(TEST.timeout);
90 | });
91 |
92 | test("requesting", function() {
93 | whenReady(function() {
94 | var req = ctx.socket(nullmq.REQ);
95 | req.connect('/rep');
96 | req.send("Foobar");
97 | control.send('rep');
98 | control.recv(function(expected) {
99 | req.recv(function(actual) {
100 | start();
101 | equals(actual, expected);
102 | });
103 |
104 | });
105 | });
106 | stop(TEST.timeout);
107 | });
108 |
109 | test("replying", function() {
110 | whenReady(function() {
111 | var expected = null;
112 | var rep = ctx.socket(nullmq.REP);
113 | rep.connect('/req');
114 | rep.recvall(function(msg) {
115 | expected = msg + ":Foobar";
116 | rep.send(expected);
117 | });
118 | control.send('req');
119 | control.recv(function(actual) {
120 | start();
121 | equals(actual, expected);
122 | });
123 | });
124 | stop(TEST.timeout);
125 | });
126 |
--------------------------------------------------------------------------------
/demos/bridge/server.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os.path
3 | import mimetypes
4 |
5 | import gevent.server
6 |
7 | from ws4py.server.geventserver import WebSocketServer
8 | from stomp4py.server.websocket import StompHandler
9 |
10 | from gevent_zeromq import zmq
11 |
12 | from bridge import ZeroMQBridge
13 |
14 | context = zmq.Context()
15 |
16 | def control_socket(prefix):
17 | control = context.socket(zmq.REP)
18 | control.bind('%s/control' % prefix)
19 | sockets = None
20 | while True:
21 | cmd = control.recv()
22 | if cmd == 'setup':
23 | if sockets is None:
24 | sockets = dict(
25 | pub=context.socket(zmq.PUB),
26 | sub=context.socket(zmq.SUB),
27 | req=context.socket(zmq.REQ),
28 | rep=context.socket(zmq.REP),
29 | push=context.socket(zmq.PUSH),
30 | pull=context.socket(zmq.PULL),)
31 | for key in sockets:
32 | sockets[key].bind('%s/%s' % (prefix, key))
33 | sockets['sub'].setsockopt(zmq.SUBSCRIBE, '')
34 | control.send("ok")
35 | elif cmd == 'teardown':
36 | #for key in sockets:
37 | # sockets[key].close()
38 | control.send("ok")
39 | elif cmd == 'pub':
40 | sockets[cmd].send("Foobar")
41 | control.send("Foobar")
42 | elif cmd == 'sub':
43 | msg = sockets[cmd].recv()
44 | control.send(msg)
45 | elif cmd == 'req':
46 | sockets[cmd].send("Foobar")
47 | reply = sockets[cmd].recv()
48 | control.send(reply)
49 | elif cmd == 'rep':
50 | request = sockets[cmd].recv()
51 | sockets[cmd].send("Foobar:%s" % request)
52 | control.send("Foobar:%s" % request)
53 | elif cmd == 'push':
54 | sockets[cmd].send("Foobar")
55 | control.send("Foobar")
56 | elif cmd == 'pull':
57 | msg = sockets[cmd].recv()
58 | control.send(msg)
59 | else:
60 | control.send("Unknown command")
61 |
62 | if __name__ == '__main__':
63 | prefix = sys.argv[1] if len(sys.argv) > 1 else 'ipc:///tmp'
64 |
65 | #gevent.spawn(control_socket, prefix)
66 |
67 | bridge = ZeroMQBridge(prefix)
68 |
69 | def websocket_handler(websocket, environ):
70 | if environ.get('PATH_INFO') == '/bridge':
71 | StompHandler(websocket, bridge).serve()
72 | else:
73 | websocket.close()
74 |
75 | def http_handler(environ, start_response):
76 | if '/dist' in environ['PATH_INFO'] and environ['PATH_INFO'].split('/')[1] == 'dist':
77 | filename = '../..%s' % environ['PATH_INFO']
78 | else:
79 | filename = environ['PATH_INFO'].split('/')[-1] or 'index.html'
80 | if os.path.exists(filename):
81 | start_response('200 OK', [('Content-type', mimetypes.guess_type(filename)[0])])
82 | return [open(filename).read()]
83 | else:
84 | start_response('404 Not found', [('Content-type', 'text/plain')])
85 | return ["Not found"]
86 |
87 | server = WebSocketServer(('127.0.0.1', 9000), websocket_handler, fallback_app=http_handler)
88 | print "Starting NullMQ-ZeroMQ bridge on 9000 for prefix %s..." % prefix
89 | server.serve_forever()
--------------------------------------------------------------------------------
/demos/bridge/bridge.py:
--------------------------------------------------------------------------------
1 | import collections
2 |
3 | import gevent
4 |
5 | from stomp4py.server.websocket import StompHandler
6 | from stomp4py.server.base import Subscription
7 |
8 | from gevent_zeromq import zmq
9 |
10 | context = zmq.Context()
11 |
12 | class NullMQConnection(Subscription):
13 | def __init__(self, handler, frame):
14 | super(NullMQConnection, self).__init__(handler, frame)
15 | self.type = frame.headers.get('type', 'connect')
16 | self.socket_type = frame.headers.get('socket')
17 | self.uid = '%s-%s' % (frame.conn_id, self.id)
18 | self.filter = frame.headers.get('filter', '')
19 | self.listener = None
20 |
21 | StompHandler.subscription_class = NullMQConnection
22 |
23 | class ZeroMQBridge(object):
24 | def __init__(self, prefix):
25 | self.prefix = prefix
26 | self.connection_by_uid = {}
27 | self.connection_by_destination = collections.defaultdict(set)
28 |
29 | def __call__(self, frame, payload):
30 | print frame
31 | if frame is None or frame.command == 'DISCONNECT':
32 | for connection in payload:
33 | self.close(connection)
34 | elif frame.command == 'SUBSCRIBE':
35 | self.connect(payload)
36 | elif frame.command == 'UNSUBSCRIBE':
37 | self.close(payload)
38 | elif frame.command == 'SEND':
39 | self.send(frame)
40 | elif frame.command == 'COMMIT':
41 | for part in payload:
42 | self.send(part)
43 |
44 | def connect(self, connection):
45 | connection.socket = context.socket(
46 | getattr(zmq, connection.socket_type.upper()))
47 | connection.socket.connect('%s%s' % (self.prefix, connection.destination))
48 |
49 | self.connection_by_uid[connection.uid] = connection
50 | self.connection_by_destination[connection.destination].add(connection)
51 | if connection.socket_type == 'sub':
52 | connection.socket.setsockopt(zmq.SUBSCRIBE, connection.filter)
53 | if connection.socket_type in ['sub', 'pull', 'dealer']:
54 | def listen_for_messages():
55 | while connection.active:
56 | connection.send(connection.socket.recv())
57 | gevent.spawn(listen_for_messages)
58 |
59 | def close(self, connection):
60 | if connection.listener:
61 | connection.listener.kill()
62 | connection.socket.close()
63 | del self.connection_by_uid[connection.uid]
64 | self.connection_by_destination[connection.destination].remove(connection)
65 |
66 | def send(self, frame):
67 | type = frame.headers.get('socket')
68 | if type in ['req', 'rep']:
69 | reply_to = frame.headers.get('reply-to')
70 | if type == 'req':
71 | uid = '%s-%s' % (frame.conn_id, reply_to)
72 | else:
73 | uid = reply_to
74 | conn = self.connection_by_uid[uid]
75 | conn.socket.send(frame.body)
76 | if type == 'req':
77 | def wait_for_reply():
78 | conn.send(conn.socket.recv())
79 | conn.listener = gevent.spawn(wait_for_reply)
80 | elif type in ['pub', 'push', 'dealer']:
81 | conns = list(self.connection_by_destination[frame.destination])
82 | if len(conns):
83 | conns[0].socket.send(frame.body)
84 |
--------------------------------------------------------------------------------
/dist/lib/qunit.css:
--------------------------------------------------------------------------------
1 |
2 | ol#qunit-tests {
3 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
4 | margin:0;
5 | padding:0;
6 | list-style-position:inside;
7 |
8 | font-size: smaller;
9 | }
10 | ol#qunit-tests li{
11 | padding:0.4em 0.5em 0.4em 2.5em;
12 | border-bottom:1px solid #fff;
13 | font-size:small;
14 | list-style-position:inside;
15 | }
16 | ol#qunit-tests li ol{
17 | box-shadow: inset 0px 2px 13px #999;
18 | -moz-box-shadow: inset 0px 2px 13px #999;
19 | -webkit-box-shadow: inset 0px 2px 13px #999;
20 | margin-top:0.5em;
21 | margin-left:0;
22 | padding:0.5em;
23 | background-color:#fff;
24 | border-radius:15px;
25 | -moz-border-radius: 15px;
26 | -webkit-border-radius: 15px;
27 | }
28 | ol#qunit-tests li li{
29 | border-bottom:none;
30 | margin:0.5em;
31 | background-color:#fff;
32 | list-style-position: inside;
33 | padding:0.4em 0.5em 0.4em 0.5em;
34 | }
35 |
36 | ol#qunit-tests li li.pass{
37 | border-left:26px solid #C6E746;
38 | background-color:#fff;
39 | color:#5E740B;
40 | }
41 | ol#qunit-tests li li.fail{
42 | border-left:26px solid #EE5757;
43 | background-color:#fff;
44 | color:#710909;
45 | }
46 | ol#qunit-tests li.pass{
47 | background-color:#D2E0E6;
48 | color:#528CE0;
49 | }
50 | ol#qunit-tests li.fail{
51 | background-color:#EE5757;
52 | color:#000;
53 | }
54 | ol#qunit-tests li strong {
55 | cursor:pointer;
56 | }
57 | h1#qunit-header{
58 | background-color:#0d3349;
59 | margin:0;
60 | padding:0.5em 0 0.5em 1em;
61 | color:#fff;
62 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
63 | border-top-right-radius:15px;
64 | border-top-left-radius:15px;
65 | -moz-border-radius-topright:15px;
66 | -moz-border-radius-topleft:15px;
67 | -webkit-border-top-right-radius:15px;
68 | -webkit-border-top-left-radius:15px;
69 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
70 | }
71 | h2#qunit-banner{
72 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
73 | height:5px;
74 | margin:0;
75 | padding:0;
76 | }
77 | h2#qunit-banner.qunit-pass{
78 | background-color:#C6E746;
79 | }
80 | h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar {
81 | background-color:#EE5757;
82 | }
83 | #qunit-testrunner-toolbar {
84 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
85 | padding:0;
86 | /*width:80%;*/
87 | padding:0em 0 0.5em 2em;
88 | font-size: small;
89 | }
90 | h2#qunit-userAgent {
91 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
92 | background-color:#2b81af;
93 | margin:0;
94 | padding:0;
95 | color:#fff;
96 | font-size: small;
97 | padding:0.5em 0 0.5em 2.5em;
98 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
99 | }
100 | p#qunit-testresult{
101 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
102 | margin:0;
103 | font-size: small;
104 | color:#2b81af;
105 | border-bottom-right-radius:15px;
106 | border-bottom-left-radius:15px;
107 | -moz-border-radius-bottomright:15px;
108 | -moz-border-radius-bottomleft:15px;
109 | -webkit-border-bottom-right-radius:15px;
110 | -webkit-border-bottom-left-radius:15px;
111 | background-color:#D2E0E6;
112 | padding:0.5em 0.5em 0.5em 2.5em;
113 | }
114 | strong b.fail{
115 | color:#710909;
116 | }
117 | strong b.pass{
118 | color:#5E740B;
119 | }
120 |
--------------------------------------------------------------------------------
/demos/presence/python/server.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os.path
3 | import mimetypes
4 | import collections
5 | import gevent
6 |
7 | from ws4py.server.geventserver import WebSocketServer
8 | from stomp4py.server.websocket import StompHandler
9 | from stomp4py.server.base import Subscription
10 |
11 | from gevent_zeromq import zmq
12 |
13 | context = zmq.Context()
14 |
15 | class NullMQConnection(Subscription):
16 | def __init__(self, handler, frame):
17 | super(NullMQConnection, self).__init__(handler, frame)
18 | self.type = frame.headers.get('type', 'connect')
19 | self.socket_type = frame.headers.get('socket')
20 | self.uid = '%s-%s' % (frame.conn_id, self.id)
21 | self.filter = frame.headers.get('filter', '')
22 | self.listener = None
23 |
24 | StompHandler.subscription_class = NullMQConnection
25 |
26 | class ZeroMQBridge(object):
27 | def __init__(self):
28 | self.connection_by_uid = {}
29 | self.connection_by_destination = collections.defaultdict(set)
30 |
31 | def __call__(self, frame, payload):
32 | if frame is None or frame.command == 'DISCONNECT':
33 | for connection in payload:
34 | self.close(connection)
35 | elif frame.command == 'SUBSCRIBE':
36 | self.connect(payload)
37 | elif frame.command == 'UNSUBSCRIBE':
38 | self.close(payload)
39 | elif frame.command == 'SEND':
40 | self.send(frame)
41 | elif frame.command == 'COMMIT':
42 | for part in payload:
43 | self.send(part)
44 |
45 | def connect(self, connection):
46 | connection.socket = context.socket(
47 | getattr(zmq, connection.socket_type.upper()))
48 | connection.socket.connect('%s' % (connection.destination))
49 |
50 | self.connection_by_uid[connection.uid] = connection
51 | self.connection_by_destination[connection.destination].add(connection)
52 | if connection.socket_type == 'sub':
53 | connection.socket.setsockopt(zmq.SUBSCRIBE, connection.filter)
54 | if connection.socket_type in ['sub', 'pull', 'dealer']:
55 | def listen_for_messages():
56 | while connection.active:
57 | connection.send(connection.socket.recv())
58 | gevent.spawn(listen_for_messages)
59 |
60 | def close(self, connection):
61 | if connection.listener:
62 | connection.listener.kill()
63 | connection.socket.close()
64 | del self.connection_by_uid[connection.uid]
65 | self.connection_by_destination[connection.destination].remove(connection)
66 |
67 | def send(self, frame):
68 | type = frame.headers.get('socket')
69 | if type in ['req', 'rep']:
70 | reply_to = frame.headers.get('reply-to')
71 | if type == 'req':
72 | uid = '%s-%s' % (frame.conn_id, reply_to)
73 | else:
74 | uid = reply_to
75 | conn = self.connection_by_uid[uid]
76 | conn.socket.send(frame.body)
77 | if type == 'req':
78 | def wait_for_reply():
79 | conn.send(conn.socket.recv())
80 | conn.listener = gevent.spawn(wait_for_reply)
81 | elif type in ['pub', 'push', 'dealer']:
82 | conns = list(self.connection_by_destination[frame.destination])
83 | if len(conns):
84 | conns[0].socket.send(frame.body)
85 |
86 | if __name__ == '__main__':
87 | def websocket_handler(websocket, environ):
88 | StompHandler(websocket, ZeroMQBridge()).serve()
89 |
90 | server = WebSocketServer(('0.0.0.0', 9000), websocket_handler)
91 | print "Starting NullMQ-ZeroMQ bridge on 9000"
92 | server.serve_forever()
--------------------------------------------------------------------------------
/demos/presence/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Presence
6 |
7 |
8 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 | Connecting...
39 | You are connected as {{name}}
40 | Disconnecting...
41 | You are not connected
42 | Client status: {{presenceClient.status}}
43 |
44 |
50 |
51 |
52 |
53 |
54 | online
55 | offline
56 |
57 | {{peer.name}}
58 | {{peer.text}}
59 |
60 |
61 |
62 |
63 |
64 |
No peers online
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | | [{{message.timestamp|timestamp}}] |
74 | <{{message.name}}> |
75 | {{message.text}} |
76 |
77 |
78 |
79 |
80 |
81 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
100 |
101 |
--------------------------------------------------------------------------------
/notes.txt:
--------------------------------------------------------------------------------
1 | requirements:
2 | jasmine-node
3 | growl
4 | coffee-script
5 |
6 | revelations:
7 | - server is not like a device. it's a shared endpoint-space
8 | - no reason to re-use subscriptions. each connection/binding should have its own subscription
9 |
10 |
11 |
12 | v0.1 out of scope
13 | - performance
14 | - multipart
15 | - reconnects
16 | - linger?
17 |
18 | NullMQ: ZeroMQ-like sockets using STOMP over WebSocket
19 |
20 | automatic reconnects
21 | dynamic message batching
22 | queue
23 |
24 | Reflector pub/sub example
25 | =================
26 | // Browser A, Publisher
27 | context = nullmq.Context('http://endpoint')
28 | socklet = context.socklet(nullmq.PUB)
29 | socklet.connect('/foo/bar')
30 | socklet.send("GET THIS")
31 |
32 | // Browser B, Subscriber
33 | context = nullmq.Context('http://endpoint')
34 | socklet = context.socklet(nullmq.SUB)
35 | socklet.connect('/foo/bar')
36 | socklet.setsockopt(nullmq.SUBSCRIBE, '')
37 | socklet.recvall(function (msg) {
38 | // got it!
39 | })
40 |
41 | Reflector req/rep example
42 | =========================
43 | // Browser A, Requester (client)
44 | context = nullmq.Context('http://endpoint')
45 | socklet = context.socklet(nullmq.REQ)
46 | socklet.connect('/service')
47 | socklet.send("give me a thing")
48 | socklet.recv(function (reply) {
49 | // yay!
50 | });
51 |
52 | // Browser A, Replyer (server)
53 | context = nullmq.Context('http://endpoint')
54 | socklet = context.socklet(nullmq.REP)
55 | socklet.bind('/service') // Bind semantics here mean "don't let others bind as REP"
56 | socklet.recvall(function (req) {
57 | socklet.send("here's your thing");
58 | });
59 |
60 |
61 |
62 | context = nullmq.Context('http://endpoint')
63 | socklet = context.socklet(nullmq.SUB)
64 | socklet.connect('/presence/updates')
65 | socklet.recv(function (msg) {
66 |
67 | })
68 | socklet.recvall(function (msg) {
69 |
70 | })
71 | socklet.send()
72 | socklet.sendmulti()
73 |
74 | socklet = conn.socklet(nullmq.REQ)
75 | socklet.connect('/presence/snapshot')
76 |
77 | socklet = conn.socklet(nullmq.PUSH)
78 | socklet.connect('/presence/update')
79 |
80 |
81 | REQ
82 | connect
83 | SUBSCRIBE
84 | destination:/named-connection
85 | identity:(socket identity if set)
86 | send (client to server)
87 | SEND
88 | destination:/named-connection
89 | request-id: 1
90 | recv (server to client)
91 | MESSAGE
92 | destination:/named-connection
93 | message-id: 123
94 | request-id: 1
95 | REP
96 | connect
97 | SUBSCRIBE
98 | destination:/named-connection
99 | identity:(socket identity if set)
100 | recv (server to client)
101 | MESSAGE
102 | destination:/named-connection
103 | message-id: 123
104 | request-id: 1
105 | send (client to server)
106 | SEND
107 | destination:/named-connection
108 | request-id: 1
109 | PUSH
110 | send (client to server)
111 | SEND
112 | destination:/named-connection
113 | PULL
114 | connect
115 | SUBSCRIBE
116 | destination:/named-connection
117 | identity:(socket identity if set)
118 | recv (server to client)
119 | MESSAGE
120 | destination:/named-connection
121 | message-id: 123
122 | PUB
123 | send (client to server)
124 | SEND
125 | destination:/named-connection
126 | SUB
127 | connect
128 | SUBSCRIBE
129 | destination:/named-connection
130 | identity:(socket identity if set)
131 | recv (server to client)
132 | MESSAGE
133 | destination:/named-connection
134 | message-id: 123
135 | PAIR
136 | connect
137 | SUBSCRIBE
138 | destination:/named-connection
139 | identity:(socket identity if set)
140 | recv (server to client)
141 | MESSAGE
142 | destination:/named-connection
143 | message-id: 123
144 | send (client to server)
145 | SEND
146 | destination:/named-connection
147 | DEALER (PUSH/PULL)
148 | connect
149 | SUBSCRIBE
150 | destination:/named-connection
151 | identity:(socket identity if set)
152 | recv (server to client)
153 | MESSAGE
154 | destination:/named-connection
155 | message-id: 123
156 | send (client to server)
157 | SEND
158 | destination:/named-connection
159 | ROUTER
160 | connect
161 | SUBSCRIBE
162 | destination:/named-connection
163 | identity:(socket identity if set)
164 | recv (server to client)
165 | MESSAGE
166 | destination:/named-connection
167 | message-id: 123
168 | send (client to server)
169 | SEND
170 | destination:/named-connection
171 |
--------------------------------------------------------------------------------
/test/server.mock.coffee:
--------------------------------------------------------------------------------
1 | {WebSocketMock} = require('./websocket.mock.js')
2 | {Stomp} = require('../lib/stomp.js')
3 |
4 | class exports.ReflectorServerMock extends WebSocketMock
5 | # WebSocketMock handlers
6 |
7 | handle_send: (msg) =>
8 | @stomp_dispatch(Stomp.unmarshal(msg))
9 |
10 | handle_close: =>
11 | @_shutdown()
12 |
13 | handle_open: =>
14 | @stomp_init()
15 | @_accept()
16 |
17 | # Stomp server implementation.
18 | # Keep in mind this Stomp server is
19 | # meant to simulate a NullMQ reflector
20 |
21 | stomp_init: ->
22 | @transactions = {}
23 | @subscriptions = {}
24 | @mailbox = []
25 | @rr_index = 0
26 | @router = setInterval =>
27 | if @readyState isnt 1 then clearInterval @router
28 | is_corresponding = (subscription, frame) ->
29 | # Returns true if destination and socket pairings match
30 | socket_routing =
31 | pub: 'sub'
32 | req: 'rep'
33 | rep: 'req'
34 | push: 'pull'
35 | subscription.destination is frame.destination and \
36 | subscription.socket is socket_routing[frame.headers.socket]
37 | while frame = @mailbox.shift()
38 | # Core routing logic of the reflector
39 | switch frame.headers['socket']
40 | when 'pub'
41 | # Fanout
42 | for id, sub of @subscriptions when is_corresponding sub, frame
43 | sub.callback(Math.random(), frame.body)
44 | when 'req'
45 | # Round robin w/ reply-to
46 | reps = (sub for id, sub of @subscriptions when is_corresponding sub, frame)
47 | reply_to = frame.headers['reply-to']
48 | @rr_index = ++@rr_index % reps.length
49 | reps[@rr_index].callback(Math.random(), frame.body, {'reply-to': reply_to})
50 | when 'rep'
51 | # Reply-to lookup
52 | reply_to = frame.headers['reply-to']
53 | @subscriptions[reply_to].callback(Math.random(), frame.body)
54 | when 'push'
55 | # Round robin
56 | pulls = (sub for id, sub of @subscriptions when is_corresponding sub, frame)
57 | @rr_index = ++@rr_index % pulls.length
58 | pulls[@rr_index].callback(Math.random(), frame.body)
59 | , 20
60 |
61 | stomp_send: (command, headers, body=null) ->
62 | @_respond(Stomp.marshal(command, headers, body))
63 |
64 | stomp_send_receipt: (frame) ->
65 | if frame.error?
66 | @stomp_send("ERROR", {'receipt-id': frame.receipt, 'message': frame.error})
67 | else
68 | @stomp_send("RECEIPT", {'receipt-id': frame.receipt})
69 |
70 | stomp_send_message: (destination, subscription, message_id, body, headers={}) ->
71 | headers['destination'] = destination
72 | headers['message-id'] = message_id
73 | headers['subscription'] = subscription
74 | @stomp_send("MESSAGE", headers, body)
75 |
76 | stomp_dispatch: (frame) ->
77 | handler = "stomp_handle_#{frame.command.toLowerCase()}"
78 | if this[handler]?
79 | this[handler](frame)
80 | if frame.receipt
81 | @stomp_send_receipt(frame)
82 | else
83 | console.log "StompServerMock: Unknown command: #{frame.command}"
84 |
85 | stomp_handle_connect: (frame) ->
86 | @session_id = Math.random()
87 | @stomp_send("CONNECTED", {'session': @session_id})
88 |
89 | stomp_handle_begin: (frame) ->
90 | @transactions[frame.transaction] = []
91 |
92 | stomp_handle_commit: (frame) ->
93 | transaction = @transactions[frame.transaction]
94 | for frame in transaction
95 | @mailbox.push(frame)
96 | delete @transactions[frame.transaction]
97 |
98 | stomp_handle_abort: (frame) ->
99 | delete @transactions[frame.transaction]
100 |
101 | stomp_handle_send: (frame) ->
102 | if frame.transaction
103 | @transactions[frame.transaction].push frame
104 | else
105 | @mailbox.push frame
106 |
107 | stomp_handle_subscribe: (frame) ->
108 | sub_id = frame.id or Math.random()
109 | cb = (id, body, headers={}) =>
110 | @stomp_send_message(frame.destination, sub_id, id, body, headers)
111 | @subscriptions[sub_id] =
112 | destination: frame.destination
113 | callback: cb
114 | type: frame.headers.type
115 | socket: frame.headers.socket
116 |
117 | stomp_handle_unsubscribe: (frame) ->
118 | if frame.id in Object.keys(@subscriptions)
119 | delete @subscriptions[frame.id]
120 | else
121 | frame.error = "Subscription does not exist"
122 |
123 | stomp_handle_disconnect: (frame) ->
124 | @_shutdown()
125 |
126 | # Test helpers
127 |
128 | test_send: (sub_id, message) ->
129 | msgid = 'msg-' + Math.random()
130 | @subscriptions[sub_id][1](msgid, message)
131 |
--------------------------------------------------------------------------------
/demos/reflector/server.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import uuid
3 | import os.path
4 | import mimetypes
5 |
6 | import gevent.server
7 |
8 | from ws4py.server.geventserver import WebSocketServer
9 | from stomp4py.server.websocket import StompHandler
10 | from stomp4py.server.base import Subscription
11 |
12 | class NullMQConnection(Subscription):
13 | def __init__(self, handler, frame):
14 | super(NullMQConnection, self).__init__(handler, frame)
15 | self.type = frame.headers.get('type', 'connect')
16 | self.socket = frame.headers.get('socket')
17 | self.route_id = '%s-%s' % (frame.conn_id, self.id)
18 |
19 | StompHandler.subscription_class = NullMQConnection
20 |
21 | def socket_pairing(subscription, frame):
22 | return subscription.socket == {'pub': 'sub', 'req': 'rep',
23 | 'rep': 'req', 'push': 'pull',}[frame.headers.get('socket')]
24 |
25 | class NullMQReflector(object):
26 | def __init__(self):
27 | self.rr_index = 0
28 | self.channels = collections.defaultdict(set)
29 | self.subscriber_counts = collections.Counter()
30 |
31 | def __call__(self, frame, payload):
32 | print frame
33 | if frame is None or frame.command == 'DISCONNECT':
34 | for subscriber in payload:
35 | self.unsubscribe(subscriber.destination, subscriber)
36 | elif frame.command == 'SUBSCRIBE':
37 | self.subscribe(frame.destination, payload)
38 | elif frame.command == 'UNSUBSCRIBE':
39 | self.unsubscribe(frame.destination, payload)
40 | elif frame.command == 'SEND':
41 | self.route(frame)
42 | elif frame.command == 'COMMIT':
43 | for part in payload:
44 | self.route(part)
45 |
46 | def subscribe(self, destination, subscriber):
47 | self.subscriber_counts[destination] += 1
48 | self.channels[destination].add(subscriber)
49 |
50 | def unsubscribe(self, destination, subscriber):
51 | self.subscriber_counts[destination] -= 1
52 | self.channels[destination].remove(subscriber)
53 |
54 | # Clean up counts and channels with no subscribers
55 | self.subscriber_counts += collections.Counter()
56 | if not self.subscriber_counts[destination]:
57 | del self.channels[destination]
58 |
59 | def route(self, frame):
60 | # Core routing logic of the reflector
61 | socket = frame.headers.get('socket')
62 | if socket == 'pub':
63 | # Fanout
64 | for subscriber in self.channels[frame.destination]:
65 | if socket_pairing(subscriber, frame):
66 | subscriber.send(frame.body)
67 | elif socket == 'req':
68 | # Round robin w/ reply-to
69 | reps = [s for s in self.channels[frame.destination] if socket_pairing(s, frame)]
70 | self.rr_index = (self.rr_index + 1) % len(reps)
71 | reply_to = '%s-%s' % (frame.conn_id, frame.headers.get('reply-to'))
72 | reps[self.rr_index].send(frame.body, {'reply-to': reply_to})
73 | elif socket == 'rep':
74 | # Reply-to lookup
75 | reply_to = frame.headers.get('reply-to')
76 | for subscriber in self.channels[frame.destination]:
77 | if subscriber.route_id == reply_to:
78 | subscriber.send(frame.body)
79 | elif socket == 'push':
80 | # Round robin
81 | reps = [s for s in self.channels[frame.destination] if socket_pairing(s, frame)]
82 | self.rr_index = (self.rr_index + 1) % len(reps)
83 | reps[self.rr_index].send(frame.body)
84 |
85 | if __name__ == '__main__':
86 | reflector = NullMQReflector()
87 |
88 | def websocket_handler(websocket, environ):
89 | if environ.get('PATH_INFO') == '/reflector':
90 | StompHandler(websocket, reflector).serve()
91 | else:
92 | websocket.close()
93 |
94 | def http_handler(environ, start_response):
95 | if '/dist' in environ['PATH_INFO'] and environ['PATH_INFO'].split('/')[1] == 'dist':
96 | filename = '../..%s' % environ['PATH_INFO']
97 | else:
98 | filename = environ['PATH_INFO'].split('/')[-1] or 'index.html'
99 | if os.path.exists(filename):
100 | start_response('200 OK', [('Content-type', mimetypes.guess_type(filename)[0])])
101 | return [open(filename).read()]
102 | else:
103 | start_response('404 Not found', [('Content-type', 'text/plain')])
104 | return ["Not found"]
105 |
106 | server = WebSocketServer(('127.0.0.1', 9000), websocket_handler, fallback_app=http_handler)
107 | print "Starting NullMQ reflector on 9000..."
108 | server.serve_forever()
--------------------------------------------------------------------------------
/test/nullmq.spec.coffee:
--------------------------------------------------------------------------------
1 | {nullmq} = require('../nullmq.js')
2 | {Stomp} = require('../lib/stomp.js')
3 | Stomp.WebSocket = require('./server.mock.js').ReflectorServerMock
4 |
5 | describe "nullmq with reflector", ->
6 | it "round robins and fans-in for PUSH-PULL pipelining", ->
7 | url = "ws://endpoint"
8 | received = 0
9 | messages = []
10 | sink = null
11 | message = "Foobar"
12 | ctx = new nullmq.Context url, ->
13 | worker1in = ctx.socket(nullmq.PULL)
14 | worker1out = ctx.socket(nullmq.PUSH)
15 | worker1in.connect('/source')
16 | worker1out.connect('/sink')
17 | worker1in.recvall (msg) ->
18 | worker1out.send "worker1: #{msg}"
19 |
20 | worker2in = ctx.socket(nullmq.PULL)
21 | worker2out = ctx.socket(nullmq.PUSH)
22 | worker2in.connect('/source')
23 | worker2out.connect('/sink')
24 | worker2in.recvall (msg) ->
25 | worker2out.send "worker2: #{msg}"
26 |
27 | sink = ctx.socket(nullmq.PULL)
28 | sink.bind('/sink')
29 | sink.recvall (msg) ->
30 | messages.push msg
31 |
32 | source = ctx.socket(nullmq.PUSH)
33 | source.bind('/source')
34 | source.send message
35 | source.send message
36 | source.send message
37 |
38 | waitsFor -> messages.length > 2
39 | runs ->
40 | expect(messages).toContain "worker1: #{message}"
41 | expect(messages).toContain "worker2: #{message}"
42 | expect(messages.length).toBe 3
43 | ctx.term()
44 |
45 | it "round robin to REP sockets over several destinations from REQ socket", ->
46 | url = "ws://endpoint"
47 | received = 0
48 | messages = []
49 | req = null
50 | ctx = new nullmq.Context url, ->
51 | rep1 = ctx.socket(nullmq.REP)
52 | rep1.connect('/destination1')
53 | rep1.recvall (msg) ->
54 | rep1.send "rep1"
55 |
56 | rep2 = ctx.socket(nullmq.REP)
57 | rep2.bind('/destination2')
58 | rep2.recvall (msg) ->
59 | rep2.send "rep2"
60 |
61 | req = ctx.socket(nullmq.REQ)
62 | req.connect('/destination1')
63 | req.connect('/destination2')
64 | req.send("foo")
65 | req.recv (reply) ->
66 | messages.push reply
67 | received++
68 |
69 | waitsFor -> received > 0
70 | runs ->
71 | expect(messages).toContain "rep1"
72 | req.send("bar")
73 | req.recv (reply) ->
74 | messages.push reply
75 | received++
76 |
77 | waitsFor -> received > 1
78 | runs ->
79 | expect(messages).toContain "rep2"
80 | ctx.term()
81 |
82 | it "does fanout when using PUB and SUB over several destinations", ->
83 | url = "ws://endpoint"
84 | received = 0
85 | [messages1, messages2, messages3] = [[],[],[]]
86 | msg = "Hello world"
87 | ctx = new nullmq.Context url, ->
88 |
89 | sub1 = ctx.socket(nullmq.SUB)
90 | sub1.connect('/destination1')
91 | sub1.setsockopt(nullmq.SUBSCRIBE, '')
92 | sub1.recvall (msg) ->
93 | messages1.push(msg)
94 | received++
95 |
96 | sub2 = ctx.socket(nullmq.SUB)
97 | sub2.connect('/destination2')
98 | sub2.setsockopt(nullmq.SUBSCRIBE, '')
99 | sub2.recvall (msg) ->
100 | messages2.push(msg)
101 | received++
102 |
103 | sub3 = ctx.socket(nullmq.SUB)
104 | sub3.connect('/destination1')
105 | sub3.connect('/destination2')
106 | sub3.setsockopt(nullmq.SUBSCRIBE, '')
107 | sub3.recvall (msg) ->
108 | messages3.push(msg)
109 | received++
110 |
111 | pub = ctx.socket(nullmq.PUB)
112 | pub.connect('/destination1')
113 | pub.connect('/destination2')
114 | pub.send(msg)
115 |
116 | waitsFor -> received >= 4
117 | runs ->
118 | expect(messages1).toContain msg
119 | expect(messages2).toContain msg
120 | expect(messages3).toContain msg
121 | expect(messages3.length).toEqual 2
122 | ctx.term()
123 |
124 |
125 | describe "nullmq namespace", ->
126 | it "can create a Context", ->
127 | url = "ws://endpoint"
128 | ctx = new nullmq.Context url, ->
129 | expect(ctx.url).toEqual url
130 | expect(ctx.sockets.length).toEqual 0
131 | ctx.term()
132 |
133 | describe "nullmq.Context", ->
134 | url = "ws://endpoint"
135 |
136 | it "can create a Socket", ->
137 | ctx = new nullmq.Context url, ->
138 | socket = ctx.socket(nullmq.REQ)
139 | expect(socket.type).toEqual nullmq.REQ
140 | ctx.term()
141 |
142 | it "closes sockets on termination", ->
143 | ctx = new nullmq.Context url, ->
144 | socket1 = ctx.socket(nullmq.REQ)
145 | socket2 = ctx.socket(nullmq.REQ)
146 | expect(socket1.closed).toEqual false
147 | expect(socket2.closed).toEqual false
148 | ctx.term()
149 | expect(socket1.closed).toEqual true
150 | expect(socket2.closed).toEqual true
151 |
--------------------------------------------------------------------------------
/demos/presence/html/js/presence-0.1.0.js:
--------------------------------------------------------------------------------
1 | MainController.$inject = ['$updateView'];
2 | function MainController($updateView) {
3 | this.STATUS_CONNECTING = Client.CONNECTING;
4 | this.STATUS_CONNECTED = Client.CONNECTED;
5 | this.STATUS_DISCONNECTED = Client.DISCONNECTED;
6 | this.STATUS_DISCONNECTING = Client.DISCONNECTING;
7 |
8 | this.peers = {};
9 |
10 | this.getPeers = function() {
11 | return Object.keys(this.peers).map(function(name) {
12 | return this.peers[name]
13 | }.bind(this))
14 | }.bind(this);
15 |
16 | this.scrollToBottom = function() {
17 | var objDiv = document.getElementById("chat_window");
18 | objDiv.scrollTop = objDiv.scrollHeight;
19 | }
20 |
21 | this.presenceClient = new Client({
22 | subscribe: "tcp://localhost:10001"
23 | , request: "tcp://localhost:10002"
24 | , push: "tcp://localhost:10003"
25 | });
26 | this.presenceClient.onResponse = function(payload) {
27 | Object.merge(this.peers, JSON.parse(payload));
28 | $updateView();
29 | }.bind(this);
30 | this.presenceClient.onPublish = function(payload) {
31 | var peer = JSON.parse(payload);
32 | this.peers[peer['name']] = this.peers[peer['name']] || {}
33 | Object.merge(this.peers[peer['name']], peer);
34 | $updateView();
35 | }.bind(this);
36 | var interval;
37 | this.presenceClient.onConnect = function() {
38 | interval = setInterval(function() {
39 | this.presenceClient.push(JSON.stringify({
40 | name: this.name
41 | , online: true
42 | , text: this.text
43 | , timeout: 2
44 | }));
45 | }.bind(this), 1000);
46 | }.bind(this);
47 | this.presenceClient.onDisconnect = function() {
48 | clearInterval(interval);
49 | this.peers = {};
50 | }.bind(this);
51 |
52 | this.messages = [];
53 | this.chatClient = new Client({
54 | subscribe: "tcp://localhost:10004"
55 | , request: "tcp://localhost:10005"
56 | , push: "tcp://localhost:10006"
57 | });
58 | this.chatClient.onResponse = function(payload) {
59 | JSON.parse(payload).forEach(function(msg) {
60 | this.messages.push(msg);
61 | }.bind(this));
62 | this.$root.$eval();
63 | this.scrollToBottom();
64 | }.bind(this);
65 | this.chatClient.onPublish = function(payload) {
66 | var msg = JSON.parse(payload);
67 | this.messages.push(msg);
68 | this.$root.$eval();
69 | this.scrollToBottom();
70 | }.bind(this);
71 | this.chatClient.onDisconnect = function() {
72 | this.messages = []
73 | }.bind(this);
74 |
75 | this.sendMessage = function() {
76 | if (this.message) {
77 | this.chatClient.push(JSON.stringify({
78 | name: this.name
79 | , text: this.message
80 | }));
81 | this.message = '';
82 | }
83 | }.bind(this)
84 |
85 | this.connect = function() {
86 | this.presenceClient.connect();
87 | this.chatClient.connect();
88 | $updateView();
89 | }.bind(this);
90 |
91 | this.disconnect = function() {
92 | this.presenceClient.disconnect();
93 | this.chatClient.disconnect();
94 | $updateView();
95 | }.bind(this);
96 | }
97 |
98 | Client.CONNECTING = 2;
99 | Client.CONNECTED = 0;
100 | Client.DISCONNECTED = 1;
101 | Client.DISCONNECTING = 3;
102 |
103 | function Client(options) {
104 | this.status = Client.DISCONNECTED;
105 | this.options = options;
106 |
107 | this.onResponse = function(payload) {};
108 | this.onPublish = function(payload) {};
109 | this.onConnect = function() {};
110 | this.onDisconnect = function() {};
111 | };
112 |
113 | Client.prototype.connect = function() {
114 | if (this.status != Client.DISCONNECTED) {
115 | return;
116 | }
117 |
118 | this.context = new nullmq.Context('ws://'+window.location.hostname+':9000');
119 | this.status = Client.CONNECTING;
120 |
121 | this.startSub();
122 | this.doRequest();
123 | this.startPush();
124 |
125 | this.status = Client.CONNECTED;
126 |
127 | this.onConnect();
128 | };
129 |
130 | Client.prototype.disconnect = function() {
131 | if (this.status != Client.CONNECTED) {
132 | return;
133 | }
134 |
135 | this.status = Client.DISCONNECTING;
136 |
137 | this.stopSub();
138 | this.stopPush();
139 | this.context.term();
140 | delete this.context;
141 |
142 | this.status = Client.DISCONNECTED;
143 |
144 | this.onDisconnect();
145 | };
146 |
147 | Client.prototype.doRequest = function() {
148 | var req = this.context.socket(nullmq.REQ);
149 | req.connect(this.options['request']);
150 | req.send('');
151 | req.recv(this.onResponse);
152 | }
153 |
154 | Client.prototype.startSub = function() {
155 | this.subSock = this.context.socket(nullmq.SUB);
156 |
157 | this.subSock.connect(this.options['subscribe']);
158 | this.subSock.setsockopt(nullmq.SUBSCRIBE, '');
159 |
160 | this.subSock.recvall(this.onPublish);
161 | }
162 |
163 | Client.prototype.stopSub = function() {
164 | (this.subSock.close || angular.noop)();
165 | }
166 |
167 | Client.prototype.startPush = function() {
168 | this.pushSock = this.context.socket(nullmq.PUSH);
169 | this.pushSock.connect(this.options['push']);
170 | }
171 |
172 | Client.prototype.stopPush = function() {
173 | (this.pushSock.close || angular.noop)();
174 | }
175 |
176 | Client.prototype.push = function(payload) {
177 | if (this.status != Client.CONNECTED) {
178 | return;
179 | }
180 | this.pushSock.send(payload);
181 | }
182 |
183 | Object.merge = function(destination, source) {
184 | for (var property in source) {
185 | if (source.hasOwnProperty(property)) {
186 | destination[property] = source[property];
187 | }
188 | }
189 | return destination;
190 | };
191 |
--------------------------------------------------------------------------------
/dist/lib/stomp.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var Client, Stomp;
3 | var __hasProp = Object.prototype.hasOwnProperty, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
4 | Stomp = {
5 | WebSocket: typeof WebSocket !== "undefined" && WebSocket !== null ? WebSocket : null,
6 | frame: function(command, headers, body) {
7 | if (headers == null) {
8 | headers = [];
9 | }
10 | if (body == null) {
11 | body = '';
12 | }
13 | return {
14 | command: command,
15 | headers: headers,
16 | body: body,
17 | id: headers.id,
18 | receipt: headers.receipt,
19 | transaction: headers.transaction,
20 | destination: headers.destination,
21 | subscription: headers.subscription,
22 | error: null,
23 | toString: function() {
24 | var lines, name, value;
25 | lines = [command];
26 | for (name in headers) {
27 | if (!__hasProp.call(headers, name)) continue;
28 | value = headers[name];
29 | lines.push("" + name + ": " + value);
30 | }
31 | lines.push('\n' + body);
32 | return lines.join('\n');
33 | }
34 | };
35 | },
36 | unmarshal: function(data) {
37 | var body, chr, command, divider, headerLines, headers, i, idx, line, trim, _ref, _ref2, _ref3;
38 | divider = data.search(/\n\n/);
39 | headerLines = data.substring(0, divider).split('\n');
40 | command = headerLines.shift();
41 | headers = {};
42 | body = '';
43 | trim = function(str) {
44 | return str.replace(/^\s+/g, '').replace(/\s+$/g, '');
45 | };
46 | line = idx = null;
47 | for (i = 0, _ref = headerLines.length; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
48 | line = headerLines[i];
49 | idx = line.indexOf(':');
50 | headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
51 | }
52 | chr = null;
53 | for (i = _ref2 = divider + 2, _ref3 = data.length; _ref2 <= _ref3 ? i < _ref3 : i > _ref3; _ref2 <= _ref3 ? i++ : i--) {
54 | chr = data.charAt(i);
55 | if (chr === '\0') {
56 | break;
57 | }
58 | body += chr;
59 | }
60 | return Stomp.frame(command, headers, body);
61 | },
62 | marshal: function(command, headers, body) {
63 | return Stomp.frame(command, headers, body).toString() + '\0';
64 | },
65 | client: function(url) {
66 | return new Client(url);
67 | }
68 | };
69 | Client = (function() {
70 | function Client(url) {
71 | this.url = url;
72 | this.counter = 0;
73 | this.connected = false;
74 | this.subscriptions = {};
75 | }
76 | Client.prototype._transmit = function(command, headers, body) {
77 | var out;
78 | out = Stomp.marshal(command, headers, body);
79 | if (typeof this.debug === "function") {
80 | this.debug(">>> " + out);
81 | }
82 | return this.ws.send(out);
83 | };
84 | Client.prototype.connect = function(login_, passcode_, connectCallback, errorCallback) {
85 | if (typeof this.debug === "function") {
86 | this.debug("Opening Web Socket...");
87 | }
88 | this.ws = new Stomp.WebSocket(this.url);
89 | this.ws.onmessage = __bind(function(evt) {
90 | var frame, onreceive;
91 | if (typeof this.debug === "function") {
92 | this.debug('<<< ' + evt.data);
93 | }
94 | frame = Stomp.unmarshal(evt.data);
95 | if (frame.command === "CONNECTED" && connectCallback) {
96 | this.connected = true;
97 | return connectCallback(frame);
98 | } else if (frame.command === "MESSAGE") {
99 | onreceive = this.subscriptions[frame.headers.subscription];
100 | return typeof onreceive === "function" ? onreceive(frame) : void 0;
101 | }
102 | }, this);
103 | this.ws.onclose = __bind(function() {
104 | var msg;
105 | msg = "Whoops! Lost connection to " + this.url;
106 | if (typeof this.debug === "function") {
107 | this.debug(msg);
108 | }
109 | return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
110 | }, this);
111 | this.ws.onopen = __bind(function() {
112 | if (typeof this.debug === "function") {
113 | this.debug('Web Socket Opened...');
114 | }
115 | return this._transmit("CONNECT", {
116 | login: login_,
117 | passcode: passcode_
118 | });
119 | }, this);
120 | return this.connectCallback = connectCallback;
121 | };
122 | Client.prototype.disconnect = function(disconnectCallback) {
123 | this._transmit("DISCONNECT");
124 | this.ws.close();
125 | this.connected = false;
126 | return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
127 | };
128 | Client.prototype.send = function(destination, headers, body) {
129 | if (headers == null) {
130 | headers = {};
131 | }
132 | if (body == null) {
133 | body = '';
134 | }
135 | headers.destination = destination;
136 | return this._transmit("SEND", headers, body);
137 | };
138 | Client.prototype.subscribe = function(destination, callback, headers) {
139 | var id;
140 | if (headers == null) {
141 | headers = {};
142 | }
143 | id = "sub-" + this.counter++;
144 | headers.destination = destination;
145 | headers.id = id;
146 | this.subscriptions[id] = callback;
147 | this._transmit("SUBSCRIBE", headers);
148 | return id;
149 | };
150 | Client.prototype.unsubscribe = function(id, headers) {
151 | if (headers == null) {
152 | headers = {};
153 | }
154 | headers.id = id;
155 | delete this.subscriptions[id];
156 | return this._transmit("UNSUBSCRIBE", headers);
157 | };
158 | Client.prototype.begin = function(transaction, headers) {
159 | if (headers == null) {
160 | headers = {};
161 | }
162 | headers.transaction = transaction;
163 | return this._transmit("BEGIN", headers);
164 | };
165 | Client.prototype.commit = function(transaction, headers) {
166 | if (headers == null) {
167 | headers = {};
168 | }
169 | headers.transaction = transaction;
170 | return this._transmit("COMMIT", headers);
171 | };
172 | Client.prototype.abort = function(transaction, headers) {
173 | if (headers == null) {
174 | headers = {};
175 | }
176 | headers.transaction = transaction;
177 | return this._transmit("ABORT", headers);
178 | };
179 | Client.prototype.ack = function(message_id, headers) {
180 | if (headers == null) {
181 | headers = {};
182 | }
183 | headers["message-id"] = message_id;
184 | return this._transmit("ACK", headers);
185 | };
186 | return Client;
187 | })();
188 | if (typeof window !== "undefined" && window !== null) {
189 | window.Stomp = Stomp;
190 | } else {
191 | exports.Stomp = Stomp;
192 | }
193 | }).call(this);
194 |
--------------------------------------------------------------------------------
/demos/presence/html/js/stomp-0.1.0.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var Client, Stomp;
3 | var __hasProp = Object.prototype.hasOwnProperty, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
4 | Stomp = {
5 | WebSocket: typeof WebSocket !== "undefined" && WebSocket !== null ? WebSocket : null,
6 | frame: function(command, headers, body) {
7 | if (headers == null) {
8 | headers = [];
9 | }
10 | if (body == null) {
11 | body = '';
12 | }
13 | return {
14 | command: command,
15 | headers: headers,
16 | body: body,
17 | id: headers.id,
18 | receipt: headers.receipt,
19 | transaction: headers.transaction,
20 | destination: headers.destination,
21 | subscription: headers.subscription,
22 | error: null,
23 | toString: function() {
24 | var lines, name, value;
25 | lines = [command];
26 | for (name in headers) {
27 | if (!__hasProp.call(headers, name)) continue;
28 | value = headers[name];
29 | lines.push("" + name + ": " + value);
30 | }
31 | lines.push('\n' + body);
32 | return lines.join('\n');
33 | }
34 | };
35 | },
36 | unmarshal: function(data) {
37 | var body, chr, command, divider, headerLines, headers, i, idx, line, trim, _ref, _ref2, _ref3;
38 | divider = data.search(/\n\n/);
39 | headerLines = data.substring(0, divider).split('\n');
40 | command = headerLines.shift();
41 | headers = {};
42 | body = '';
43 | trim = function(str) {
44 | return str.replace(/^\s+/g, '').replace(/\s+$/g, '');
45 | };
46 | line = idx = null;
47 | for (i = 0, _ref = headerLines.length; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
48 | line = headerLines[i];
49 | idx = line.indexOf(':');
50 | headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
51 | }
52 | chr = null;
53 | for (i = _ref2 = divider + 2, _ref3 = data.length; _ref2 <= _ref3 ? i < _ref3 : i > _ref3; _ref2 <= _ref3 ? i++ : i--) {
54 | chr = data.charAt(i);
55 | if (chr === '\0') {
56 | break;
57 | }
58 | body += chr;
59 | }
60 | return Stomp.frame(command, headers, body);
61 | },
62 | marshal: function(command, headers, body) {
63 | return Stomp.frame(command, headers, body).toString() + '\0';
64 | },
65 | client: function(url) {
66 | return new Client(url);
67 | }
68 | };
69 | Client = (function() {
70 | function Client(url) {
71 | this.url = url;
72 | this.counter = 0;
73 | this.connected = false;
74 | this.subscriptions = {};
75 | }
76 | Client.prototype._transmit = function(command, headers, body) {
77 | var out;
78 | out = Stomp.marshal(command, headers, body);
79 | if (typeof this.debug === "function") {
80 | this.debug(">>> " + out);
81 | }
82 | return this.ws.send(out);
83 | };
84 | Client.prototype.connect = function(login_, passcode_, connectCallback, errorCallback) {
85 | if (typeof this.debug === "function") {
86 | this.debug("Opening Web Socket...");
87 | }
88 | this.ws = new Stomp.WebSocket(this.url);
89 | this.ws.onmessage = __bind(function(evt) {
90 | var frame, onreceive;
91 | if (typeof this.debug === "function") {
92 | this.debug('<<< ' + evt.data);
93 | }
94 | frame = Stomp.unmarshal(evt.data);
95 | if (frame.command === "CONNECTED" && connectCallback) {
96 | this.connected = true;
97 | return connectCallback(frame);
98 | } else if (frame.command === "MESSAGE") {
99 | onreceive = this.subscriptions[frame.headers.subscription];
100 | return typeof onreceive === "function" ? onreceive(frame) : void 0;
101 | }
102 | }, this);
103 | this.ws.onclose = __bind(function() {
104 | var msg;
105 | msg = "Whoops! Lost connection to " + this.url;
106 | if (typeof this.debug === "function") {
107 | this.debug(msg);
108 | }
109 | return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
110 | }, this);
111 | this.ws.onopen = __bind(function() {
112 | if (typeof this.debug === "function") {
113 | this.debug('Web Socket Opened...');
114 | }
115 | return this._transmit("CONNECT", {
116 | login: login_,
117 | passcode: passcode_
118 | });
119 | }, this);
120 | return this.connectCallback = connectCallback;
121 | };
122 | Client.prototype.disconnect = function(disconnectCallback) {
123 | this._transmit("DISCONNECT");
124 | this.ws.close();
125 | this.connected = false;
126 | return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
127 | };
128 | Client.prototype.send = function(destination, headers, body) {
129 | if (headers == null) {
130 | headers = {};
131 | }
132 | if (body == null) {
133 | body = '';
134 | }
135 | headers.destination = destination;
136 | return this._transmit("SEND", headers, body);
137 | };
138 | Client.prototype.subscribe = function(destination, callback, headers) {
139 | var id;
140 | if (headers == null) {
141 | headers = {};
142 | }
143 | id = "sub-" + this.counter++;
144 | headers.destination = destination;
145 | headers.id = id;
146 | this.subscriptions[id] = callback;
147 | this._transmit("SUBSCRIBE", headers);
148 | return id;
149 | };
150 | Client.prototype.unsubscribe = function(id, headers) {
151 | if (headers == null) {
152 | headers = {};
153 | }
154 | headers.id = id;
155 | delete this.subscriptions[id];
156 | return this._transmit("UNSUBSCRIBE", headers);
157 | };
158 | Client.prototype.begin = function(transaction, headers) {
159 | if (headers == null) {
160 | headers = {};
161 | }
162 | headers.transaction = transaction;
163 | return this._transmit("BEGIN", headers);
164 | };
165 | Client.prototype.commit = function(transaction, headers) {
166 | if (headers == null) {
167 | headers = {};
168 | }
169 | headers.transaction = transaction;
170 | return this._transmit("COMMIT", headers);
171 | };
172 | Client.prototype.abort = function(transaction, headers) {
173 | if (headers == null) {
174 | headers = {};
175 | }
176 | headers.transaction = transaction;
177 | return this._transmit("ABORT", headers);
178 | };
179 | Client.prototype.ack = function(message_id, headers) {
180 | if (headers == null) {
181 | headers = {};
182 | }
183 | headers["message-id"] = message_id;
184 | return this._transmit("ACK", headers);
185 | };
186 | return Client;
187 | })();
188 | if (typeof window !== "undefined" && window !== null) {
189 | window.Stomp = Stomp;
190 | } else {
191 | exports.Stomp = Stomp;
192 | }
193 | }).call(this);
194 |
--------------------------------------------------------------------------------
/src/nullmq.coffee:
--------------------------------------------------------------------------------
1 | nullmq =
2 | # socket types
3 | #PAIR: 'pair' # Not sold we should support PAIR
4 | PUB: 'pub'
5 | SUB: 'sub'
6 | REQ: 'req'
7 | REP: 'rep'
8 | XREQ: 'dealer' # Deprecated in favor of DEALER
9 | XREP: 'router' # Deprecated in favor of ROUTER
10 | PULL: 'pull'
11 | PUSH: 'push'
12 | DEALER: 'dealer'
13 | ROUTER: 'router'
14 |
15 | # socket options
16 | HWM: 100
17 | IDENTITY: 101
18 | SUBSCRIBE: 102
19 | UNSUBSCRIBE: 103
20 | #LINGER: 104
21 | #RECONNECT_IVL_MAX: 105
22 | #RECONNECT_IVL: 106
23 | #RCVMORE: 107
24 | #SNDMORE: 108
25 |
26 | _SENDERS: ['req', 'dealer', 'push', 'pub', 'router', 'rep']
27 |
28 | assert = (description, condition=false) ->
29 | # We assert assumed state so we can more easily catch bugs.
30 | # Do not assert if we *know* the user can get to it.
31 | throw Error "Assertion: #{description}" if not condition
32 |
33 | class Queue
34 | # Originally based on the implementation here:
35 | # http://code.stephenmorley.org/javascript/queues/
36 |
37 | constructor: (@maxsize=null) ->
38 | @queue = []
39 | @offset = 0
40 | @watches = []
41 |
42 | getLength: ->
43 | @queue.length - @offset
44 |
45 | isEmpty: ->
46 | @queue.length is 0
47 |
48 | isFull: ->
49 | if @maxsize is null then return false
50 | return @getLength() >= @maxsize
51 |
52 | put: (item) ->
53 | if not @isFull()
54 | @queue.push item
55 | @watches.shift()?()
56 | return item
57 | else
58 | return undefined
59 |
60 | get: ->
61 | if @queue.length is 0 then return undefined
62 | item = @queue[@offset]
63 | if ++@offset*2 >= @queue.length
64 | @queue = @queue.slice(@offset)
65 | @offset = 0
66 | item
67 |
68 | peek: ->
69 | if @queue.length > 0 then @queue[@offset] else undefined
70 |
71 | watch: (fn) ->
72 | if @queue.length is 0
73 | @watches.push(fn)
74 | else
75 | fn()
76 |
77 | class nullmq.Context
78 | constructor: (@url, onconnect=->) ->
79 | @active = false
80 | @client = Stomp.client(@url)
81 | @client.connect "guest", "guest", =>
82 | @active = true
83 | op() while op = @pending_operations.shift()
84 | @pending_operations = [onconnect]
85 | @sockets = []
86 |
87 | socket: (type) ->
88 | new Socket this, type
89 |
90 | term: ->
91 | @_when_connected =>
92 | assert "context is already connected", @client.connected
93 | for socket in @sockets
94 | socket.close()
95 | @client.disconnect()
96 |
97 | _send: (socket, destination, message) ->
98 | @_when_connected =>
99 | assert "context is already connected", @client.connected
100 | headers = {'socket': socket.type}
101 | if socket.type is nullmq.REQ
102 | headers['reply-to'] = socket.connections[destination]
103 | if socket.type is nullmq.REP
104 | headers['reply-to'] = socket.last_recv.reply_to
105 | if message instanceof Array
106 | headers['transaction'] = Math.random()+''
107 | @client.begin transaction
108 | for part in message
109 | @client.send destination, headers, part
110 | @client.commit transaction
111 | else
112 | @client.send destination, headers, message.toString()
113 |
114 | _subscribe: (type, socket, destination) ->
115 | @_when_connected =>
116 | assert "context is already connected", @client.connected
117 | id = @client.subscribe destination, (frame) =>
118 | envelope = {'message': frame.body, 'destination': frame.destination}
119 | if frame.headers['reply-to']?
120 | envelope['reply_to'] = frame.headers['reply-to']
121 | socket.recv_queue.put envelope
122 | , {'socket': socket.type, 'type': type}
123 | socket.connections[destination] = id
124 |
125 | _connect: (socket, destination) -> @_subscribe('connect', socket, destination)
126 | _bind: (socket, destination) -> @_subscribe('bind', socket, destination)
127 |
128 | _when_connected: (op) ->
129 | if @client.connected then op() else @pending_operations.push op
130 |
131 |
132 | class Socket
133 | constructor: (@context, @type) ->
134 | @client = @context.client
135 | @closed = false
136 | @recv_queue = new Queue()
137 | @send_queue = new Queue()
138 | @identity = null
139 | @linger = -1
140 | @filters = []
141 | @connections = {}
142 | @rr_index = 0 # round-robin counter
143 | @last_recv = undefined
144 | @context.sockets.push this
145 | if @type in nullmq._SENDERS
146 | @send_queue.watch @_dispatch_outgoing
147 |
148 | connect: (destination) ->
149 | if destination in Object.keys(@connections) then return
150 | @context._connect this, destination
151 |
152 | bind: (destination) ->
153 | if destination in Object.keys(@connections) then return
154 | @context._bind this, destination
155 |
156 | setsockopt: (option, value) ->
157 | switch option
158 | when nullmq.HWM then @hwm = value
159 | when nullmq.IDENTITY then @_identity value
160 | when nullmq.LINGER then @linger = value
161 | when nullmq.SUBSCRIBE
162 | if @type isnt nullmq.SUB then return undefined
163 | if not value in @filters
164 | @filters.push value
165 | value
166 | when nullmq.UNSUBSCRIBE
167 | if @type isnt nullmq.SUB then return undefined
168 | if value in @filters
169 | @filters.splice @filters.indexOf(value), 1
170 | value
171 | else undefined
172 |
173 | getsockopt: (option) ->
174 | switch option
175 | when nullmq.HWM then @hwm
176 | when nullmq.IDENTITY then @identity
177 | when nullmq.LINGER then @linger
178 | else undefined
179 |
180 | close: ->
181 | for destination, id of @connections
182 | @client.unsubscribe id
183 | @connections = {}
184 | @closed = true
185 |
186 | send: (message) ->
187 | if @type in [nullmq.PULL, nullmq.SUB]
188 | throw Error("Sending is not implemented for this socket type")
189 | @send_queue.put(message)
190 |
191 | recv: (callback) ->
192 | @recv_queue.watch =>
193 | callback @_recv()
194 |
195 | recvall: (callback) ->
196 | watcher = =>
197 | callback @_recv()
198 | @recv_queue.watch watcher
199 | @recv_queue.watch watcher
200 |
201 | _recv: ->
202 | envelope = @recv_queue.get()
203 | @last_recv = envelope
204 | envelope.message
205 |
206 | _identity: (value) ->
207 | @identity = value
208 |
209 | _deliver_round_robin: (message) ->
210 | destination = Object.keys(@connections)[@rr_index]
211 | @context._send this, destination, message
212 | connection_count = Object.keys(@connections).length
213 | @rr_index = ++@rr_index % connection_count
214 |
215 | _deliver_fanout: (message) ->
216 | for destination, id of @connections
217 | @context._send this, destination, message
218 |
219 | _deliver_routed: (message) ->
220 | destination = message.shift()
221 | @context._send this, destination, message
222 |
223 | _deliver_back: (message) ->
224 | @context._send this, @last_recv.destination, message
225 |
226 | _dispatch_outgoing: =>
227 | if @context.active
228 | message = @send_queue.get()
229 | switch @type
230 | when nullmq.REQ, nullmq.DEALER, nullmq.PUSH
231 | @_deliver_round_robin message
232 | when nullmq.PUB
233 | @_deliver_fanout message
234 | when nullmq.ROUTER
235 | @_deliver_routed message
236 | when nullmq.REP
237 | @_deliver_back message
238 | else
239 | assert "outgoing dispatching shouldn't happen for this socket type"
240 | @send_queue.watch @_dispatch_outgoing
241 | else
242 | setTimeout @_dispatch_outgoing, 20
243 |
244 | if window?
245 | # For use in the browser
246 | window.nullmq = nullmq
247 | if not window.Stomp?
248 | console.log "Required Stomp library not loaded."
249 | else
250 | Stomp = window.Stomp
251 | else
252 | # For testing
253 | exports.nullmq = nullmq
254 | exports.Queue = Queue
255 | {Stomp} = require('./lib/stomp.js')
256 |
--------------------------------------------------------------------------------
/dist/nullmq.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var Queue, Socket, Stomp, assert, nullmq,
3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
4 | __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
5 |
6 | nullmq = {
7 | PUB: 'pub',
8 | SUB: 'sub',
9 | REQ: 'req',
10 | REP: 'rep',
11 | XREQ: 'dealer',
12 | XREP: 'router',
13 | PULL: 'pull',
14 | PUSH: 'push',
15 | DEALER: 'dealer',
16 | ROUTER: 'router',
17 | HWM: 100,
18 | IDENTITY: 101,
19 | SUBSCRIBE: 102,
20 | UNSUBSCRIBE: 103,
21 | _SENDERS: ['req', 'dealer', 'push', 'pub', 'router', 'rep']
22 | };
23 |
24 | assert = function(description, condition) {
25 | if (condition == null) condition = false;
26 | if (!condition) throw Error("Assertion: " + description);
27 | };
28 |
29 | Queue = (function() {
30 |
31 | function Queue(maxsize) {
32 | this.maxsize = maxsize != null ? maxsize : null;
33 | this.queue = [];
34 | this.offset = 0;
35 | this.watches = [];
36 | }
37 |
38 | Queue.prototype.getLength = function() {
39 | return this.queue.length - this.offset;
40 | };
41 |
42 | Queue.prototype.isEmpty = function() {
43 | return this.queue.length === 0;
44 | };
45 |
46 | Queue.prototype.isFull = function() {
47 | if (this.maxsize === null) return false;
48 | return this.getLength() >= this.maxsize;
49 | };
50 |
51 | Queue.prototype.put = function(item) {
52 | var _base;
53 | if (!this.isFull()) {
54 | this.queue.push(item);
55 | if (typeof (_base = this.watches.shift()) === "function") _base();
56 | return item;
57 | } else {
58 |
59 | }
60 | };
61 |
62 | Queue.prototype.get = function() {
63 | var item;
64 | if (this.queue.length === 0) return;
65 | item = this.queue[this.offset];
66 | if (++this.offset * 2 >= this.queue.length) {
67 | this.queue = this.queue.slice(this.offset);
68 | this.offset = 0;
69 | }
70 | return item;
71 | };
72 |
73 | Queue.prototype.peek = function() {
74 | if (this.queue.length > 0) {
75 | return this.queue[this.offset];
76 | } else {
77 | return;
78 | }
79 | };
80 |
81 | Queue.prototype.watch = function(fn) {
82 | if (this.queue.length === 0) {
83 | return this.watches.push(fn);
84 | } else {
85 | return fn();
86 | }
87 | };
88 |
89 | return Queue;
90 |
91 | })();
92 |
93 | nullmq.Context = (function() {
94 |
95 | function Context(url, onconnect) {
96 | var _this = this;
97 | this.url = url;
98 | if (onconnect == null) onconnect = function() {};
99 | this.active = false;
100 | this.client = Stomp.client(this.url);
101 | this.client.connect("guest", "guest", function() {
102 | var op, _results;
103 | _this.active = true;
104 | _results = [];
105 | while (op = _this.pending_operations.shift()) {
106 | _results.push(op());
107 | }
108 | return _results;
109 | });
110 | this.pending_operations = [onconnect];
111 | this.sockets = [];
112 | }
113 |
114 | Context.prototype.socket = function(type) {
115 | return new Socket(this, type);
116 | };
117 |
118 | Context.prototype.term = function() {
119 | var _this = this;
120 | return this._when_connected(function() {
121 | var socket, _i, _len, _ref;
122 | assert("context is already connected", _this.client.connected);
123 | _ref = _this.sockets;
124 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
125 | socket = _ref[_i];
126 | socket.close();
127 | }
128 | return _this.client.disconnect();
129 | });
130 | };
131 |
132 | Context.prototype._send = function(socket, destination, message) {
133 | var _this = this;
134 | return this._when_connected(function() {
135 | var headers, part, _i, _len;
136 | assert("context is already connected", _this.client.connected);
137 | headers = {
138 | 'socket': socket.type
139 | };
140 | if (socket.type === nullmq.REQ) {
141 | headers['reply-to'] = socket.connections[destination];
142 | }
143 | if (socket.type === nullmq.REP) {
144 | headers['reply-to'] = socket.last_recv.reply_to;
145 | }
146 | if (message instanceof Array) {
147 | headers['transaction'] = Math.random() + '';
148 | _this.client.begin(transaction);
149 | for (_i = 0, _len = message.length; _i < _len; _i++) {
150 | part = message[_i];
151 | _this.client.send(destination, headers, part);
152 | }
153 | return _this.client.commit(transaction);
154 | } else {
155 | return _this.client.send(destination, headers, message.toString());
156 | }
157 | });
158 | };
159 |
160 | Context.prototype._subscribe = function(type, socket, destination) {
161 | var _this = this;
162 | return this._when_connected(function() {
163 | var id;
164 | assert("context is already connected", _this.client.connected);
165 | id = _this.client.subscribe(destination, function(frame) {
166 | var envelope;
167 | envelope = {
168 | 'message': frame.body,
169 | 'destination': frame.destination
170 | };
171 | if (frame.headers['reply-to'] != null) {
172 | envelope['reply_to'] = frame.headers['reply-to'];
173 | }
174 | return socket.recv_queue.put(envelope);
175 | }, {
176 | 'socket': socket.type,
177 | 'type': type
178 | });
179 | return socket.connections[destination] = id;
180 | });
181 | };
182 |
183 | Context.prototype._connect = function(socket, destination) {
184 | return this._subscribe('connect', socket, destination);
185 | };
186 |
187 | Context.prototype._bind = function(socket, destination) {
188 | return this._subscribe('bind', socket, destination);
189 | };
190 |
191 | Context.prototype._when_connected = function(op) {
192 | if (this.client.connected) {
193 | return op();
194 | } else {
195 | return this.pending_operations.push(op);
196 | }
197 | };
198 |
199 | return Context;
200 |
201 | })();
202 |
203 | Socket = (function() {
204 |
205 | function Socket(context, type) {
206 | var _ref;
207 | this.context = context;
208 | this.type = type;
209 | this._dispatch_outgoing = __bind(this._dispatch_outgoing, this);
210 | this.client = this.context.client;
211 | this.closed = false;
212 | this.recv_queue = new Queue();
213 | this.send_queue = new Queue();
214 | this.identity = null;
215 | this.linger = -1;
216 | this.filters = [];
217 | this.connections = {};
218 | this.rr_index = 0;
219 | this.last_recv = void 0;
220 | this.context.sockets.push(this);
221 | if (_ref = this.type, __indexOf.call(nullmq._SENDERS, _ref) >= 0) {
222 | this.send_queue.watch(this._dispatch_outgoing);
223 | }
224 | }
225 |
226 | Socket.prototype.connect = function(destination) {
227 | if (__indexOf.call(Object.keys(this.connections), destination) >= 0) return;
228 | return this.context._connect(this, destination);
229 | };
230 |
231 | Socket.prototype.bind = function(destination) {
232 | if (__indexOf.call(Object.keys(this.connections), destination) >= 0) return;
233 | return this.context._bind(this, destination);
234 | };
235 |
236 | Socket.prototype.setsockopt = function(option, value) {
237 | var _ref;
238 | switch (option) {
239 | case nullmq.HWM:
240 | return this.hwm = value;
241 | case nullmq.IDENTITY:
242 | return this._identity(value);
243 | case nullmq.LINGER:
244 | return this.linger = value;
245 | case nullmq.SUBSCRIBE:
246 | if (this.type !== nullmq.SUB) return;
247 | if (_ref = !value, __indexOf.call(this.filters, _ref) >= 0) {
248 | this.filters.push(value);
249 | }
250 | return value;
251 | case nullmq.UNSUBSCRIBE:
252 | if (this.type !== nullmq.SUB) return;
253 | if (__indexOf.call(this.filters, value) >= 0) {
254 | this.filters.splice(this.filters.indexOf(value), 1);
255 | }
256 | return value;
257 | default:
258 | return;
259 | }
260 | };
261 |
262 | Socket.prototype.getsockopt = function(option) {
263 | switch (option) {
264 | case nullmq.HWM:
265 | return this.hwm;
266 | case nullmq.IDENTITY:
267 | return this.identity;
268 | case nullmq.LINGER:
269 | return this.linger;
270 | default:
271 | return;
272 | }
273 | };
274 |
275 | Socket.prototype.close = function() {
276 | var destination, id, _ref;
277 | _ref = this.connections;
278 | for (destination in _ref) {
279 | id = _ref[destination];
280 | this.client.unsubscribe(id);
281 | }
282 | this.connections = {};
283 | return this.closed = true;
284 | };
285 |
286 | Socket.prototype.send = function(message) {
287 | var _ref;
288 | if ((_ref = this.type) === nullmq.PULL || _ref === nullmq.SUB) {
289 | throw Error("Sending is not implemented for this socket type");
290 | }
291 | return this.send_queue.put(message);
292 | };
293 |
294 | Socket.prototype.recv = function(callback) {
295 | var _this = this;
296 | return this.recv_queue.watch(function() {
297 | return callback(_this._recv());
298 | });
299 | };
300 |
301 | Socket.prototype.recvall = function(callback) {
302 | var watcher,
303 | _this = this;
304 | watcher = function() {
305 | callback(_this._recv());
306 | return _this.recv_queue.watch(watcher);
307 | };
308 | return this.recv_queue.watch(watcher);
309 | };
310 |
311 | Socket.prototype._recv = function() {
312 | var envelope;
313 | envelope = this.recv_queue.get();
314 | this.last_recv = envelope;
315 | return envelope.message;
316 | };
317 |
318 | Socket.prototype._identity = function(value) {
319 | return this.identity = value;
320 | };
321 |
322 | Socket.prototype._deliver_round_robin = function(message) {
323 | var connection_count, destination;
324 | destination = Object.keys(this.connections)[this.rr_index];
325 | this.context._send(this, destination, message);
326 | connection_count = Object.keys(this.connections).length;
327 | return this.rr_index = ++this.rr_index % connection_count;
328 | };
329 |
330 | Socket.prototype._deliver_fanout = function(message) {
331 | var destination, id, _ref, _results;
332 | _ref = this.connections;
333 | _results = [];
334 | for (destination in _ref) {
335 | id = _ref[destination];
336 | _results.push(this.context._send(this, destination, message));
337 | }
338 | return _results;
339 | };
340 |
341 | Socket.prototype._deliver_routed = function(message) {
342 | var destination;
343 | destination = message.shift();
344 | return this.context._send(this, destination, message);
345 | };
346 |
347 | Socket.prototype._deliver_back = function(message) {
348 | return this.context._send(this, this.last_recv.destination, message);
349 | };
350 |
351 | Socket.prototype._dispatch_outgoing = function() {
352 | var message;
353 | if (this.context.active) {
354 | message = this.send_queue.get();
355 | switch (this.type) {
356 | case nullmq.REQ:
357 | case nullmq.DEALER:
358 | case nullmq.PUSH:
359 | this._deliver_round_robin(message);
360 | break;
361 | case nullmq.PUB:
362 | this._deliver_fanout(message);
363 | break;
364 | case nullmq.ROUTER:
365 | this._deliver_routed(message);
366 | break;
367 | case nullmq.REP:
368 | this._deliver_back(message);
369 | break;
370 | default:
371 | assert("outgoing dispatching shouldn't happen for this socket type");
372 | }
373 | return this.send_queue.watch(this._dispatch_outgoing);
374 | } else {
375 | return setTimeout(this._dispatch_outgoing, 20);
376 | }
377 | };
378 |
379 | return Socket;
380 |
381 | })();
382 |
383 | if (typeof window !== "undefined" && window !== null) {
384 | window.nullmq = nullmq;
385 | if (!(window.Stomp != null)) {
386 | console.log("Required Stomp library not loaded.");
387 | } else {
388 | Stomp = window.Stomp;
389 | }
390 | } else {
391 | exports.nullmq = nullmq;
392 | exports.Queue = Queue;
393 | Stomp = require('./lib/stomp.js').Stomp;
394 | }
395 |
396 | }).call(this);
397 |
--------------------------------------------------------------------------------
/demos/presence/html/js/nullmq-0.1.0.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var Queue, Socket, Stomp, assert, nullmq,
3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
4 | __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
5 |
6 | nullmq = {
7 | PUB: 'pub',
8 | SUB: 'sub',
9 | REQ: 'req',
10 | REP: 'rep',
11 | XREQ: 'dealer',
12 | XREP: 'router',
13 | PULL: 'pull',
14 | PUSH: 'push',
15 | DEALER: 'dealer',
16 | ROUTER: 'router',
17 | HWM: 100,
18 | IDENTITY: 101,
19 | SUBSCRIBE: 102,
20 | UNSUBSCRIBE: 103,
21 | _SENDERS: ['req', 'dealer', 'push', 'pub', 'router', 'rep']
22 | };
23 |
24 | assert = function(description, condition) {
25 | if (condition == null) condition = false;
26 | if (!condition) throw Error("Assertion: " + description);
27 | };
28 |
29 | Queue = (function() {
30 |
31 | function Queue(maxsize) {
32 | this.maxsize = maxsize != null ? maxsize : null;
33 | this.queue = [];
34 | this.offset = 0;
35 | this.watches = [];
36 | }
37 |
38 | Queue.prototype.getLength = function() {
39 | return this.queue.length - this.offset;
40 | };
41 |
42 | Queue.prototype.isEmpty = function() {
43 | return this.queue.length === 0;
44 | };
45 |
46 | Queue.prototype.isFull = function() {
47 | if (this.maxsize === null) return false;
48 | return this.getLength() >= this.maxsize;
49 | };
50 |
51 | Queue.prototype.put = function(item) {
52 | var _base;
53 | if (!this.isFull()) {
54 | this.queue.push(item);
55 | if (typeof (_base = this.watches.shift()) === "function") _base();
56 | return item;
57 | } else {
58 |
59 | }
60 | };
61 |
62 | Queue.prototype.get = function() {
63 | var item;
64 | if (this.queue.length === 0) return;
65 | item = this.queue[this.offset];
66 | if (++this.offset * 2 >= this.queue.length) {
67 | this.queue = this.queue.slice(this.offset);
68 | this.offset = 0;
69 | }
70 | return item;
71 | };
72 |
73 | Queue.prototype.peek = function() {
74 | if (this.queue.length > 0) {
75 | return this.queue[this.offset];
76 | } else {
77 | return;
78 | }
79 | };
80 |
81 | Queue.prototype.watch = function(fn) {
82 | if (this.queue.length === 0) {
83 | return this.watches.push(fn);
84 | } else {
85 | return fn();
86 | }
87 | };
88 |
89 | return Queue;
90 |
91 | })();
92 |
93 | nullmq.Context = (function() {
94 |
95 | function Context(url, onconnect) {
96 | var _this = this;
97 | this.url = url;
98 | if (onconnect == null) onconnect = function() {};
99 | this.active = false;
100 | this.client = Stomp.client(this.url);
101 | this.client.connect("guest", "guest", function() {
102 | var op, _results;
103 | _this.active = true;
104 | _results = [];
105 | while (op = _this.pending_operations.shift()) {
106 | _results.push(op());
107 | }
108 | return _results;
109 | });
110 | this.pending_operations = [onconnect];
111 | this.sockets = [];
112 | }
113 |
114 | Context.prototype.socket = function(type) {
115 | return new Socket(this, type);
116 | };
117 |
118 | Context.prototype.term = function() {
119 | var _this = this;
120 | return this._when_connected(function() {
121 | var socket, _i, _len, _ref;
122 | assert("context is already connected", _this.client.connected);
123 | _ref = _this.sockets;
124 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
125 | socket = _ref[_i];
126 | socket.close();
127 | }
128 | return _this.client.disconnect();
129 | });
130 | };
131 |
132 | Context.prototype._send = function(socket, destination, message) {
133 | var _this = this;
134 | return this._when_connected(function() {
135 | var headers, part, _i, _len;
136 | assert("context is already connected", _this.client.connected);
137 | headers = {
138 | 'socket': socket.type
139 | };
140 | if (socket.type === nullmq.REQ) {
141 | headers['reply-to'] = socket.connections[destination];
142 | }
143 | if (socket.type === nullmq.REP) {
144 | headers['reply-to'] = socket.last_recv.reply_to;
145 | }
146 | if (message instanceof Array) {
147 | headers['transaction'] = Math.random() + '';
148 | _this.client.begin(transaction);
149 | for (_i = 0, _len = message.length; _i < _len; _i++) {
150 | part = message[_i];
151 | _this.client.send(destination, headers, part);
152 | }
153 | return _this.client.commit(transaction);
154 | } else {
155 | return _this.client.send(destination, headers, message.toString());
156 | }
157 | });
158 | };
159 |
160 | Context.prototype._subscribe = function(type, socket, destination) {
161 | var _this = this;
162 | return this._when_connected(function() {
163 | var id;
164 | assert("context is already connected", _this.client.connected);
165 | id = _this.client.subscribe(destination, function(frame) {
166 | var envelope;
167 | envelope = {
168 | 'message': frame.body,
169 | 'destination': frame.destination
170 | };
171 | if (frame.headers['reply-to'] != null) {
172 | envelope['reply_to'] = frame.headers['reply-to'];
173 | }
174 | return socket.recv_queue.put(envelope);
175 | }, {
176 | 'socket': socket.type,
177 | 'type': type
178 | });
179 | return socket.connections[destination] = id;
180 | });
181 | };
182 |
183 | Context.prototype._connect = function(socket, destination) {
184 | return this._subscribe('connect', socket, destination);
185 | };
186 |
187 | Context.prototype._bind = function(socket, destination) {
188 | return this._subscribe('bind', socket, destination);
189 | };
190 |
191 | Context.prototype._when_connected = function(op) {
192 | if (this.client.connected) {
193 | return op();
194 | } else {
195 | return this.pending_operations.push(op);
196 | }
197 | };
198 |
199 | return Context;
200 |
201 | })();
202 |
203 | Socket = (function() {
204 |
205 | function Socket(context, type) {
206 | var _ref;
207 | this.context = context;
208 | this.type = type;
209 | this._dispatch_outgoing = __bind(this._dispatch_outgoing, this);
210 | this.client = this.context.client;
211 | this.closed = false;
212 | this.recv_queue = new Queue();
213 | this.send_queue = new Queue();
214 | this.identity = null;
215 | this.linger = -1;
216 | this.filters = [];
217 | this.connections = {};
218 | this.rr_index = 0;
219 | this.last_recv = void 0;
220 | this.context.sockets.push(this);
221 | if (_ref = this.type, __indexOf.call(nullmq._SENDERS, _ref) >= 0) {
222 | this.send_queue.watch(this._dispatch_outgoing);
223 | }
224 | }
225 |
226 | Socket.prototype.connect = function(destination) {
227 | if (__indexOf.call(Object.keys(this.connections), destination) >= 0) return;
228 | return this.context._connect(this, destination);
229 | };
230 |
231 | Socket.prototype.bind = function(destination) {
232 | if (__indexOf.call(Object.keys(this.connections), destination) >= 0) return;
233 | return this.context._bind(this, destination);
234 | };
235 |
236 | Socket.prototype.setsockopt = function(option, value) {
237 | var _ref;
238 | switch (option) {
239 | case nullmq.HWM:
240 | return this.hwm = value;
241 | case nullmq.IDENTITY:
242 | return this._identity(value);
243 | case nullmq.LINGER:
244 | return this.linger = value;
245 | case nullmq.SUBSCRIBE:
246 | if (this.type !== nullmq.SUB) return;
247 | if (_ref = !value, __indexOf.call(this.filters, _ref) >= 0) {
248 | this.filters.push(value);
249 | }
250 | return value;
251 | case nullmq.UNSUBSCRIBE:
252 | if (this.type !== nullmq.SUB) return;
253 | if (__indexOf.call(this.filters, value) >= 0) {
254 | this.filters.splice(this.filters.indexOf(value), 1);
255 | }
256 | return value;
257 | default:
258 | return;
259 | }
260 | };
261 |
262 | Socket.prototype.getsockopt = function(option) {
263 | switch (option) {
264 | case nullmq.HWM:
265 | return this.hwm;
266 | case nullmq.IDENTITY:
267 | return this.identity;
268 | case nullmq.LINGER:
269 | return this.linger;
270 | default:
271 | return;
272 | }
273 | };
274 |
275 | Socket.prototype.close = function() {
276 | var destination, id, _ref;
277 | _ref = this.connections;
278 | for (destination in _ref) {
279 | id = _ref[destination];
280 | this.client.unsubscribe(id);
281 | }
282 | this.connections = {};
283 | return this.closed = true;
284 | };
285 |
286 | Socket.prototype.send = function(message) {
287 | var _ref;
288 | if ((_ref = this.type) === nullmq.PULL || _ref === nullmq.SUB) {
289 | throw Error("Sending is not implemented for this socket type");
290 | }
291 | return this.send_queue.put(message);
292 | };
293 |
294 | Socket.prototype.recv = function(callback) {
295 | var _this = this;
296 | return this.recv_queue.watch(function() {
297 | return callback(_this._recv());
298 | });
299 | };
300 |
301 | Socket.prototype.recvall = function(callback) {
302 | var watcher,
303 | _this = this;
304 | watcher = function() {
305 | callback(_this._recv());
306 | return _this.recv_queue.watch(watcher);
307 | };
308 | return this.recv_queue.watch(watcher);
309 | };
310 |
311 | Socket.prototype._recv = function() {
312 | var envelope;
313 | envelope = this.recv_queue.get();
314 | this.last_recv = envelope;
315 | return envelope.message;
316 | };
317 |
318 | Socket.prototype._identity = function(value) {
319 | return this.identity = value;
320 | };
321 |
322 | Socket.prototype._deliver_round_robin = function(message) {
323 | var connection_count, destination;
324 | destination = Object.keys(this.connections)[this.rr_index];
325 | this.context._send(this, destination, message);
326 | connection_count = Object.keys(this.connections).length;
327 | return this.rr_index = ++this.rr_index % connection_count;
328 | };
329 |
330 | Socket.prototype._deliver_fanout = function(message) {
331 | var destination, id, _ref, _results;
332 | _ref = this.connections;
333 | _results = [];
334 | for (destination in _ref) {
335 | id = _ref[destination];
336 | _results.push(this.context._send(this, destination, message));
337 | }
338 | return _results;
339 | };
340 |
341 | Socket.prototype._deliver_routed = function(message) {
342 | var destination;
343 | destination = message.shift();
344 | return this.context._send(this, destination, message);
345 | };
346 |
347 | Socket.prototype._deliver_back = function(message) {
348 | return this.context._send(this, this.last_recv.destination, message);
349 | };
350 |
351 | Socket.prototype._dispatch_outgoing = function() {
352 | var message;
353 | if (this.context.active) {
354 | message = this.send_queue.get();
355 | switch (this.type) {
356 | case nullmq.REQ:
357 | case nullmq.DEALER:
358 | case nullmq.PUSH:
359 | this._deliver_round_robin(message);
360 | break;
361 | case nullmq.PUB:
362 | this._deliver_fanout(message);
363 | break;
364 | case nullmq.ROUTER:
365 | this._deliver_routed(message);
366 | break;
367 | case nullmq.REP:
368 | this._deliver_back(message);
369 | break;
370 | default:
371 | assert("outgoing dispatching shouldn't happen for this socket type");
372 | }
373 | return this.send_queue.watch(this._dispatch_outgoing);
374 | } else {
375 | return setTimeout(this._dispatch_outgoing, 20);
376 | }
377 | };
378 |
379 | return Socket;
380 |
381 | })();
382 |
383 | if (typeof window !== "undefined" && window !== null) {
384 | window.nullmq = nullmq;
385 | if (!(window.Stomp != null)) {
386 | console.log("Required Stomp library not loaded.");
387 | } else {
388 | Stomp = window.Stomp;
389 | }
390 | } else {
391 | exports.nullmq = nullmq;
392 | exports.Queue = Queue;
393 | Stomp = require('./lib/stomp.js').Stomp;
394 | }
395 |
396 | }).call(this);
397 |
--------------------------------------------------------------------------------
/demos/presence/html/css/bootstrap-responsive-2.0.1.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.0.1
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */
10 | .clearfix {
11 | *zoom: 1;
12 | }
13 | .clearfix:before, .clearfix:after {
14 | display: table;
15 | content: "";
16 | }
17 | .clearfix:after {
18 | clear: both;
19 | }
20 | .hidden {
21 | display: none;
22 | visibility: hidden;
23 | }
24 | @media (max-width: 480px) {
25 | .nav-collapse {
26 | -webkit-transform: translate3d(0, 0, 0);
27 | }
28 | .page-header h1 small {
29 | display: block;
30 | line-height: 18px;
31 | }
32 | input[class*="span"],
33 | select[class*="span"],
34 | textarea[class*="span"],
35 | .uneditable-input {
36 | display: block;
37 | width: 100%;
38 | min-height: 28px;
39 | /* Make inputs at least the height of their button counterpart */
40 |
41 | /* Makes inputs behave like true block-level elements */
42 |
43 | -webkit-box-sizing: border-box;
44 | /* Older Webkit */
45 |
46 | -moz-box-sizing: border-box;
47 | /* Older FF */
48 |
49 | -ms-box-sizing: border-box;
50 | /* IE8 */
51 |
52 | box-sizing: border-box;
53 | /* CSS3 spec*/
54 |
55 | }
56 | .input-prepend input[class*="span"], .input-append input[class*="span"] {
57 | width: auto;
58 | }
59 | input[type="checkbox"], input[type="radio"] {
60 | border: 1px solid #ccc;
61 | }
62 | .form-horizontal .control-group > label {
63 | float: none;
64 | width: auto;
65 | padding-top: 0;
66 | text-align: left;
67 | }
68 | .form-horizontal .controls {
69 | margin-left: 0;
70 | }
71 | .form-horizontal .control-list {
72 | padding-top: 0;
73 | }
74 | .form-horizontal .form-actions {
75 | padding-left: 10px;
76 | padding-right: 10px;
77 | }
78 | .modal {
79 | position: absolute;
80 | top: 10px;
81 | left: 10px;
82 | right: 10px;
83 | width: auto;
84 | margin: 0;
85 | }
86 | .modal.fade.in {
87 | top: auto;
88 | }
89 | .modal-header .close {
90 | padding: 10px;
91 | margin: -10px;
92 | }
93 | .carousel-caption {
94 | position: static;
95 | }
96 | }
97 | @media (max-width: 767px) {
98 | .container {
99 | width: auto;
100 | padding: 0 20px;
101 | }
102 | .row-fluid {
103 | width: 100%;
104 | }
105 | .row {
106 | margin-left: 0;
107 | }
108 | .row > [class*="span"], .row-fluid > [class*="span"] {
109 | float: none;
110 | display: block;
111 | width: auto;
112 | margin: 0;
113 | }
114 | }
115 | @media (min-width: 768px) and (max-width: 979px) {
116 | .row {
117 | margin-left: -20px;
118 | *zoom: 1;
119 | }
120 | .row:before, .row:after {
121 | display: table;
122 | content: "";
123 | }
124 | .row:after {
125 | clear: both;
126 | }
127 | [class*="span"] {
128 | float: left;
129 | margin-left: 20px;
130 | }
131 | .span1 {
132 | width: 42px;
133 | }
134 | .span2 {
135 | width: 104px;
136 | }
137 | .span3 {
138 | width: 166px;
139 | }
140 | .span4 {
141 | width: 228px;
142 | }
143 | .span5 {
144 | width: 290px;
145 | }
146 | .span6 {
147 | width: 352px;
148 | }
149 | .span7 {
150 | width: 414px;
151 | }
152 | .span8 {
153 | width: 476px;
154 | }
155 | .span9 {
156 | width: 538px;
157 | }
158 | .span10 {
159 | width: 600px;
160 | }
161 | .span11 {
162 | width: 662px;
163 | }
164 | .span12, .container {
165 | width: 724px;
166 | }
167 | .offset1 {
168 | margin-left: 82px;
169 | }
170 | .offset2 {
171 | margin-left: 144px;
172 | }
173 | .offset3 {
174 | margin-left: 206px;
175 | }
176 | .offset4 {
177 | margin-left: 268px;
178 | }
179 | .offset5 {
180 | margin-left: 330px;
181 | }
182 | .offset6 {
183 | margin-left: 392px;
184 | }
185 | .offset7 {
186 | margin-left: 454px;
187 | }
188 | .offset8 {
189 | margin-left: 516px;
190 | }
191 | .offset9 {
192 | margin-left: 578px;
193 | }
194 | .offset10 {
195 | margin-left: 640px;
196 | }
197 | .offset11 {
198 | margin-left: 702px;
199 | }
200 | .row-fluid {
201 | width: 100%;
202 | *zoom: 1;
203 | }
204 | .row-fluid:before, .row-fluid:after {
205 | display: table;
206 | content: "";
207 | }
208 | .row-fluid:after {
209 | clear: both;
210 | }
211 | .row-fluid > [class*="span"] {
212 | float: left;
213 | margin-left: 2.762430939%;
214 | }
215 | .row-fluid > [class*="span"]:first-child {
216 | margin-left: 0;
217 | }
218 | .row-fluid > .span1 {
219 | width: 5.801104972%;
220 | }
221 | .row-fluid > .span2 {
222 | width: 14.364640883%;
223 | }
224 | .row-fluid > .span3 {
225 | width: 22.928176794%;
226 | }
227 | .row-fluid > .span4 {
228 | width: 31.491712705%;
229 | }
230 | .row-fluid > .span5 {
231 | width: 40.055248616%;
232 | }
233 | .row-fluid > .span6 {
234 | width: 48.618784527%;
235 | }
236 | .row-fluid > .span7 {
237 | width: 57.182320438000005%;
238 | }
239 | .row-fluid > .span8 {
240 | width: 65.74585634900001%;
241 | }
242 | .row-fluid > .span9 {
243 | width: 74.30939226%;
244 | }
245 | .row-fluid > .span10 {
246 | width: 82.87292817100001%;
247 | }
248 | .row-fluid > .span11 {
249 | width: 91.436464082%;
250 | }
251 | .row-fluid > .span12 {
252 | width: 99.999999993%;
253 | }
254 | input.span1, textarea.span1, .uneditable-input.span1 {
255 | width: 32px;
256 | }
257 | input.span2, textarea.span2, .uneditable-input.span2 {
258 | width: 94px;
259 | }
260 | input.span3, textarea.span3, .uneditable-input.span3 {
261 | width: 156px;
262 | }
263 | input.span4, textarea.span4, .uneditable-input.span4 {
264 | width: 218px;
265 | }
266 | input.span5, textarea.span5, .uneditable-input.span5 {
267 | width: 280px;
268 | }
269 | input.span6, textarea.span6, .uneditable-input.span6 {
270 | width: 342px;
271 | }
272 | input.span7, textarea.span7, .uneditable-input.span7 {
273 | width: 404px;
274 | }
275 | input.span8, textarea.span8, .uneditable-input.span8 {
276 | width: 466px;
277 | }
278 | input.span9, textarea.span9, .uneditable-input.span9 {
279 | width: 528px;
280 | }
281 | input.span10, textarea.span10, .uneditable-input.span10 {
282 | width: 590px;
283 | }
284 | input.span11, textarea.span11, .uneditable-input.span11 {
285 | width: 652px;
286 | }
287 | input.span12, textarea.span12, .uneditable-input.span12 {
288 | width: 714px;
289 | }
290 | }
291 | @media (max-width: 979px) {
292 | body {
293 | padding-top: 0;
294 | }
295 | .navbar-fixed-top {
296 | position: static;
297 | margin-bottom: 18px;
298 | }
299 | .navbar-fixed-top .navbar-inner {
300 | padding: 5px;
301 | }
302 | .navbar .container {
303 | width: auto;
304 | padding: 0;
305 | }
306 | .navbar .brand {
307 | padding-left: 10px;
308 | padding-right: 10px;
309 | margin: 0 0 0 -5px;
310 | }
311 | .navbar .nav-collapse {
312 | clear: left;
313 | }
314 | .navbar .nav {
315 | float: none;
316 | margin: 0 0 9px;
317 | }
318 | .navbar .nav > li {
319 | float: none;
320 | }
321 | .navbar .nav > li > a {
322 | margin-bottom: 2px;
323 | }
324 | .navbar .nav > .divider-vertical {
325 | display: none;
326 | }
327 | .navbar .nav .nav-header {
328 | color: #999999;
329 | text-shadow: none;
330 | }
331 | .navbar .nav > li > a, .navbar .dropdown-menu a {
332 | padding: 6px 15px;
333 | font-weight: bold;
334 | color: #999999;
335 | -webkit-border-radius: 3px;
336 | -moz-border-radius: 3px;
337 | border-radius: 3px;
338 | }
339 | .navbar .dropdown-menu li + li a {
340 | margin-bottom: 2px;
341 | }
342 | .navbar .nav > li > a:hover, .navbar .dropdown-menu a:hover {
343 | background-color: #222222;
344 | }
345 | .navbar .dropdown-menu {
346 | position: static;
347 | top: auto;
348 | left: auto;
349 | float: none;
350 | display: block;
351 | max-width: none;
352 | margin: 0 15px;
353 | padding: 0;
354 | background-color: transparent;
355 | border: none;
356 | -webkit-border-radius: 0;
357 | -moz-border-radius: 0;
358 | border-radius: 0;
359 | -webkit-box-shadow: none;
360 | -moz-box-shadow: none;
361 | box-shadow: none;
362 | }
363 | .navbar .dropdown-menu:before, .navbar .dropdown-menu:after {
364 | display: none;
365 | }
366 | .navbar .dropdown-menu .divider {
367 | display: none;
368 | }
369 | .navbar-form, .navbar-search {
370 | float: none;
371 | padding: 9px 15px;
372 | margin: 9px 0;
373 | border-top: 1px solid #222222;
374 | border-bottom: 1px solid #222222;
375 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
376 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
377 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
378 | }
379 | .navbar .nav.pull-right {
380 | float: none;
381 | margin-left: 0;
382 | }
383 | .navbar-static .navbar-inner {
384 | padding-left: 10px;
385 | padding-right: 10px;
386 | }
387 | .btn-navbar {
388 | display: block;
389 | }
390 | .nav-collapse {
391 | overflow: hidden;
392 | height: 0;
393 | }
394 | }
395 | @media (min-width: 980px) {
396 | .nav-collapse.collapse {
397 | height: auto !important;
398 | }
399 | }
400 | @media (min-width: 1200px) {
401 | .row {
402 | margin-left: -30px;
403 | *zoom: 1;
404 | }
405 | .row:before, .row:after {
406 | display: table;
407 | content: "";
408 | }
409 | .row:after {
410 | clear: both;
411 | }
412 | [class*="span"] {
413 | float: left;
414 | margin-left: 30px;
415 | }
416 | .span1 {
417 | width: 70px;
418 | }
419 | .span2 {
420 | width: 170px;
421 | }
422 | .span3 {
423 | width: 270px;
424 | }
425 | .span4 {
426 | width: 370px;
427 | }
428 | .span5 {
429 | width: 470px;
430 | }
431 | .span6 {
432 | width: 570px;
433 | }
434 | .span7 {
435 | width: 670px;
436 | }
437 | .span8 {
438 | width: 770px;
439 | }
440 | .span9 {
441 | width: 870px;
442 | }
443 | .span10 {
444 | width: 970px;
445 | }
446 | .span11 {
447 | width: 1070px;
448 | }
449 | .span12, .container {
450 | width: 1170px;
451 | }
452 | .offset1 {
453 | margin-left: 130px;
454 | }
455 | .offset2 {
456 | margin-left: 230px;
457 | }
458 | .offset3 {
459 | margin-left: 330px;
460 | }
461 | .offset4 {
462 | margin-left: 430px;
463 | }
464 | .offset5 {
465 | margin-left: 530px;
466 | }
467 | .offset6 {
468 | margin-left: 630px;
469 | }
470 | .offset7 {
471 | margin-left: 730px;
472 | }
473 | .offset8 {
474 | margin-left: 830px;
475 | }
476 | .offset9 {
477 | margin-left: 930px;
478 | }
479 | .offset10 {
480 | margin-left: 1030px;
481 | }
482 | .offset11 {
483 | margin-left: 1130px;
484 | }
485 | .row-fluid {
486 | width: 100%;
487 | *zoom: 1;
488 | }
489 | .row-fluid:before, .row-fluid:after {
490 | display: table;
491 | content: "";
492 | }
493 | .row-fluid:after {
494 | clear: both;
495 | }
496 | .row-fluid > [class*="span"] {
497 | float: left;
498 | margin-left: 2.564102564%;
499 | }
500 | .row-fluid > [class*="span"]:first-child {
501 | margin-left: 0;
502 | }
503 | .row-fluid > .span1 {
504 | width: 5.982905983%;
505 | }
506 | .row-fluid > .span2 {
507 | width: 14.529914530000001%;
508 | }
509 | .row-fluid > .span3 {
510 | width: 23.076923077%;
511 | }
512 | .row-fluid > .span4 {
513 | width: 31.623931624%;
514 | }
515 | .row-fluid > .span5 {
516 | width: 40.170940171000005%;
517 | }
518 | .row-fluid > .span6 {
519 | width: 48.717948718%;
520 | }
521 | .row-fluid > .span7 {
522 | width: 57.264957265%;
523 | }
524 | .row-fluid > .span8 {
525 | width: 65.81196581200001%;
526 | }
527 | .row-fluid > .span9 {
528 | width: 74.358974359%;
529 | }
530 | .row-fluid > .span10 {
531 | width: 82.905982906%;
532 | }
533 | .row-fluid > .span11 {
534 | width: 91.45299145300001%;
535 | }
536 | .row-fluid > .span12 {
537 | width: 100%;
538 | }
539 | input.span1, textarea.span1, .uneditable-input.span1 {
540 | width: 60px;
541 | }
542 | input.span2, textarea.span2, .uneditable-input.span2 {
543 | width: 160px;
544 | }
545 | input.span3, textarea.span3, .uneditable-input.span3 {
546 | width: 260px;
547 | }
548 | input.span4, textarea.span4, .uneditable-input.span4 {
549 | width: 360px;
550 | }
551 | input.span5, textarea.span5, .uneditable-input.span5 {
552 | width: 460px;
553 | }
554 | input.span6, textarea.span6, .uneditable-input.span6 {
555 | width: 560px;
556 | }
557 | input.span7, textarea.span7, .uneditable-input.span7 {
558 | width: 660px;
559 | }
560 | input.span8, textarea.span8, .uneditable-input.span8 {
561 | width: 760px;
562 | }
563 | input.span9, textarea.span9, .uneditable-input.span9 {
564 | width: 860px;
565 | }
566 | input.span10, textarea.span10, .uneditable-input.span10 {
567 | width: 960px;
568 | }
569 | input.span11, textarea.span11, .uneditable-input.span11 {
570 | width: 1060px;
571 | }
572 | input.span12, textarea.span12, .uneditable-input.span12 {
573 | width: 1160px;
574 | }
575 | .thumbnails {
576 | margin-left: -30px;
577 | }
578 | .thumbnails > li {
579 | margin-left: 30px;
580 | }
581 | }
582 |
--------------------------------------------------------------------------------
/dist/lib/qunit.js:
--------------------------------------------------------------------------------
1 | /*
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * and GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | (function(window) {
12 |
13 | var QUnit = {
14 |
15 | // Initialize the configuration options
16 | init: function() {
17 | config = {
18 | stats: { all: 0, bad: 0 },
19 | moduleStats: { all: 0, bad: 0 },
20 | started: +new Date,
21 | blocking: false,
22 | autorun: false,
23 | assertions: [],
24 | filters: [],
25 | queue: []
26 | };
27 |
28 | var tests = id("qunit-tests"),
29 | banner = id("qunit-banner"),
30 | result = id("qunit-testresult");
31 |
32 | if ( tests ) {
33 | tests.innerHTML = "";
34 | }
35 |
36 | if ( banner ) {
37 | banner.className = "";
38 | }
39 |
40 | if ( result ) {
41 | result.parentNode.removeChild( result );
42 | }
43 | },
44 |
45 | // call on start of module test to prepend name to all tests
46 | module: function(name, testEnvironment) {
47 | config.currentModule = name;
48 |
49 | synchronize(function() {
50 | if ( config.currentModule ) {
51 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
52 | }
53 |
54 | config.currentModule = name;
55 | config.moduleTestEnvironment = testEnvironment;
56 | config.moduleStats = { all: 0, bad: 0 };
57 |
58 | QUnit.moduleStart( name, testEnvironment );
59 | });
60 | },
61 |
62 | asyncTest: function(testName, expected, callback) {
63 | if ( arguments.length === 2 ) {
64 | callback = expected;
65 | expected = 0;
66 | }
67 |
68 | QUnit.test(testName, expected, callback, true);
69 | },
70 |
71 | test: function(testName, expected, callback, async) {
72 | var name = testName, testEnvironment, testEnvironmentArg;
73 |
74 | if ( arguments.length === 2 ) {
75 | callback = expected;
76 | expected = null;
77 | }
78 | // is 2nd argument a testEnvironment?
79 | if ( expected && typeof expected === 'object') {
80 | testEnvironmentArg = expected;
81 | expected = null;
82 | }
83 |
84 | if ( config.currentModule ) {
85 | name = config.currentModule + " module: " + name;
86 | }
87 |
88 | if ( !validTest(name) ) {
89 | return;
90 | }
91 |
92 | synchronize(function() {
93 | QUnit.testStart( testName );
94 |
95 | testEnvironment = extend({
96 | setup: function() {},
97 | teardown: function() {}
98 | }, config.moduleTestEnvironment);
99 | if (testEnvironmentArg) {
100 | extend(testEnvironment,testEnvironmentArg);
101 | }
102 |
103 | // allow utility functions to access the current test environment
104 | QUnit.current_testEnvironment = testEnvironment;
105 |
106 | config.assertions = [];
107 | config.expected = expected;
108 |
109 | try {
110 | if ( !config.pollution ) {
111 | saveGlobal();
112 | }
113 |
114 | testEnvironment.setup.call(testEnvironment);
115 | } catch(e) {
116 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
117 | }
118 |
119 | if ( async ) {
120 | QUnit.stop();
121 | }
122 |
123 | try {
124 | callback.call(testEnvironment);
125 | } catch(e) {
126 | fail("Test " + name + " died, exception and test follows", e, callback);
127 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
128 | // else next test will carry the responsibility
129 | saveGlobal();
130 |
131 | // Restart the tests if they're blocking
132 | if ( config.blocking ) {
133 | start();
134 | }
135 | }
136 | });
137 |
138 | synchronize(function() {
139 | try {
140 | checkPollution();
141 | testEnvironment.teardown.call(testEnvironment);
142 | } catch(e) {
143 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
144 | }
145 |
146 | try {
147 | QUnit.reset();
148 | } catch(e) {
149 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
150 | }
151 |
152 | if ( config.expected && config.expected != config.assertions.length ) {
153 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
154 | }
155 |
156 | var good = 0, bad = 0,
157 | tests = id("qunit-tests");
158 |
159 | config.stats.all += config.assertions.length;
160 | config.moduleStats.all += config.assertions.length;
161 |
162 | if ( tests ) {
163 | var ol = document.createElement("ol");
164 | ol.style.display = "none";
165 |
166 | for ( var i = 0; i < config.assertions.length; i++ ) {
167 | var assertion = config.assertions[i];
168 |
169 | var li = document.createElement("li");
170 | li.className = assertion.result ? "pass" : "fail";
171 | li.appendChild(document.createTextNode(assertion.message || "(no message)"));
172 | ol.appendChild( li );
173 |
174 | if ( assertion.result ) {
175 | good++;
176 | } else {
177 | bad++;
178 | config.stats.bad++;
179 | config.moduleStats.bad++;
180 | }
181 | }
182 |
183 | var b = document.createElement("strong");
184 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")";
185 |
186 | addEvent(b, "click", function() {
187 | var next = b.nextSibling, display = next.style.display;
188 | next.style.display = display === "none" ? "block" : "none";
189 | });
190 |
191 | addEvent(b, "dblclick", function(e) {
192 | var target = e && e.target ? e.target : window.event.srcElement;
193 | if ( target.nodeName.toLowerCase() === "strong" ) {
194 | var text = "", node = target.firstChild;
195 |
196 | while ( node.nodeType === 3 ) {
197 | text += node.nodeValue;
198 | node = node.nextSibling;
199 | }
200 |
201 | text = text.replace(/(^\s*|\s*$)/g, "");
202 |
203 | if ( window.location ) {
204 | window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text);
205 | }
206 | }
207 | });
208 |
209 | var li = document.createElement("li");
210 | li.className = bad ? "fail" : "pass";
211 | li.appendChild( b );
212 | li.appendChild( ol );
213 | tests.appendChild( li );
214 |
215 | if ( bad ) {
216 | var toolbar = id("qunit-testrunner-toolbar");
217 | if ( toolbar ) {
218 | toolbar.style.display = "block";
219 | id("qunit-filter-pass").disabled = null;
220 | id("qunit-filter-missing").disabled = null;
221 | }
222 | }
223 |
224 | } else {
225 | for ( var i = 0; i < config.assertions.length; i++ ) {
226 | if ( !config.assertions[i].result ) {
227 | bad++;
228 | config.stats.bad++;
229 | config.moduleStats.bad++;
230 | }
231 | }
232 | }
233 |
234 | QUnit.testDone( testName, bad, config.assertions.length );
235 |
236 | if ( !window.setTimeout && !config.queue.length ) {
237 | done();
238 | }
239 | });
240 |
241 | if ( window.setTimeout && !config.doneTimer ) {
242 | config.doneTimer = window.setTimeout(function(){
243 | if ( !config.queue.length ) {
244 | done();
245 | } else {
246 | synchronize( done );
247 | }
248 | }, 13);
249 | }
250 | },
251 |
252 | /**
253 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
254 | */
255 | expect: function(asserts) {
256 | config.expected = asserts;
257 | },
258 |
259 | /**
260 | * Asserts true.
261 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
262 | */
263 | ok: function(a, msg) {
264 | QUnit.log(a, msg);
265 |
266 | config.assertions.push({
267 | result: !!a,
268 | message: msg
269 | });
270 | },
271 |
272 | /**
273 | * Checks that the first two arguments are equal, with an optional message.
274 | * Prints out both actual and expected values.
275 | *
276 | * Prefered to ok( actual == expected, message )
277 | *
278 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
279 | *
280 | * @param Object actual
281 | * @param Object expected
282 | * @param String message (optional)
283 | */
284 | equal: function(actual, expected, message) {
285 | push(expected == actual, actual, expected, message);
286 | },
287 |
288 | notEqual: function(actual, expected, message) {
289 | push(expected != actual, actual, expected, message);
290 | },
291 |
292 | deepEqual: function(a, b, message) {
293 | push(QUnit.equiv(a, b), a, b, message);
294 | },
295 |
296 | notDeepEqual: function(a, b, message) {
297 | push(!QUnit.equiv(a, b), a, b, message);
298 | },
299 |
300 | strictEqual: function(actual, expected, message) {
301 | push(expected === actual, actual, expected, message);
302 | },
303 |
304 | notStrictEqual: function(actual, expected, message) {
305 | push(expected !== actual, actual, expected, message);
306 | },
307 |
308 | start: function() {
309 | // A slight delay, to avoid any current callbacks
310 | if ( window.setTimeout ) {
311 | window.setTimeout(function() {
312 | if ( config.timeout ) {
313 | clearTimeout(config.timeout);
314 | }
315 |
316 | config.blocking = false;
317 | process();
318 | }, 13);
319 | } else {
320 | config.blocking = false;
321 | process();
322 | }
323 | },
324 |
325 | stop: function(timeout) {
326 | config.blocking = true;
327 |
328 | if ( timeout && window.setTimeout ) {
329 | config.timeout = window.setTimeout(function() {
330 | QUnit.ok( false, "Test timed out" );
331 | QUnit.start();
332 | }, timeout);
333 | }
334 | },
335 |
336 | /**
337 | * Resets the test setup. Useful for tests that modify the DOM.
338 | */
339 | reset: function() {
340 | if ( window.jQuery ) {
341 | jQuery("#main").html( config.fixture );
342 | jQuery.event.global = {};
343 | jQuery.ajaxSettings = extend({}, config.ajaxSettings);
344 | }
345 | },
346 |
347 | /**
348 | * Trigger an event on an element.
349 | *
350 | * @example triggerEvent( document.body, "click" );
351 | *
352 | * @param DOMElement elem
353 | * @param String type
354 | */
355 | triggerEvent: function( elem, type, event ) {
356 | if ( document.createEvent ) {
357 | event = document.createEvent("MouseEvents");
358 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
359 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
360 | elem.dispatchEvent( event );
361 |
362 | } else if ( elem.fireEvent ) {
363 | elem.fireEvent("on"+type);
364 | }
365 | },
366 |
367 | // Safe object type checking
368 | is: function( type, obj ) {
369 | return Object.prototype.toString.call( obj ) === "[object "+ type +"]";
370 | },
371 |
372 | // Logging callbacks
373 | done: function(failures, total) {},
374 | log: function(result, message) {},
375 | testStart: function(name) {},
376 | testDone: function(name, failures, total) {},
377 | moduleStart: function(name, testEnvironment) {},
378 | moduleDone: function(name, failures, total) {}
379 | };
380 |
381 | // Backwards compatibility, deprecated
382 | QUnit.equals = QUnit.equal;
383 | QUnit.same = QUnit.deepEqual;
384 |
385 | // Maintain internal state
386 | var config = {
387 | // The queue of tests to run
388 | queue: [],
389 |
390 | // block until document ready
391 | blocking: true
392 | };
393 |
394 | // Load paramaters
395 | (function() {
396 | var location = window.location || { search: "", protocol: "file:" },
397 | GETParams = location.search.slice(1).split('&');
398 |
399 | for ( var i = 0; i < GETParams.length; i++ ) {
400 | GETParams[i] = decodeURIComponent( GETParams[i] );
401 | if ( GETParams[i] === "noglobals" ) {
402 | GETParams.splice( i, 1 );
403 | i--;
404 | config.noglobals = true;
405 | } else if ( GETParams[i].search('=') > -1 ) {
406 | GETParams.splice( i, 1 );
407 | i--;
408 | }
409 | }
410 |
411 | // restrict modules/tests by get parameters
412 | config.filters = GETParams;
413 |
414 | // Figure out if we're running the tests from a server or not
415 | QUnit.isLocal = !!(location.protocol === 'file:');
416 | })();
417 |
418 | // Expose the API as global variables, unless an 'exports'
419 | // object exists, in that case we assume we're in CommonJS
420 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
421 | extend(window, QUnit);
422 | window.QUnit = QUnit;
423 | } else {
424 | extend(exports, QUnit);
425 | exports.QUnit = QUnit;
426 | }
427 |
428 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
429 | config.autorun = true;
430 | }
431 |
432 | addEvent(window, "load", function() {
433 | // Initialize the config, saving the execution queue
434 | var oldconfig = extend({}, config);
435 | QUnit.init();
436 | extend(config, oldconfig);
437 |
438 | config.blocking = false;
439 |
440 | var userAgent = id("qunit-userAgent");
441 | if ( userAgent ) {
442 | userAgent.innerHTML = navigator.userAgent;
443 | }
444 |
445 | var toolbar = id("qunit-testrunner-toolbar");
446 | if ( toolbar ) {
447 | toolbar.style.display = "none";
448 |
449 | var filter = document.createElement("input");
450 | filter.type = "checkbox";
451 | filter.id = "qunit-filter-pass";
452 | filter.disabled = true;
453 | addEvent( filter, "click", function() {
454 | var li = document.getElementsByTagName("li");
455 | for ( var i = 0; i < li.length; i++ ) {
456 | if ( li[i].className.indexOf("pass") > -1 ) {
457 | li[i].style.display = filter.checked ? "none" : "";
458 | }
459 | }
460 | });
461 | toolbar.appendChild( filter );
462 |
463 | var label = document.createElement("label");
464 | label.setAttribute("for", "qunit-filter-pass");
465 | label.innerHTML = "Hide passed tests";
466 | toolbar.appendChild( label );
467 |
468 | var missing = document.createElement("input");
469 | missing.type = "checkbox";
470 | missing.id = "qunit-filter-missing";
471 | missing.disabled = true;
472 | addEvent( missing, "click", function() {
473 | var li = document.getElementsByTagName("li");
474 | for ( var i = 0; i < li.length; i++ ) {
475 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
476 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
477 | }
478 | }
479 | });
480 | toolbar.appendChild( missing );
481 |
482 | label = document.createElement("label");
483 | label.setAttribute("for", "qunit-filter-missing");
484 | label.innerHTML = "Hide missing tests (untested code is broken code)";
485 | toolbar.appendChild( label );
486 | }
487 |
488 | var main = id('main');
489 | if ( main ) {
490 | config.fixture = main.innerHTML;
491 | }
492 |
493 | if ( window.jQuery ) {
494 | config.ajaxSettings = window.jQuery.ajaxSettings;
495 | }
496 |
497 | QUnit.start();
498 | });
499 |
500 | function done() {
501 | if ( config.doneTimer && window.clearTimeout ) {
502 | window.clearTimeout( config.doneTimer );
503 | config.doneTimer = null;
504 | }
505 |
506 | if ( config.queue.length ) {
507 | config.doneTimer = window.setTimeout(function(){
508 | if ( !config.queue.length ) {
509 | done();
510 | } else {
511 | synchronize( done );
512 | }
513 | }, 13);
514 |
515 | return;
516 | }
517 |
518 | config.autorun = true;
519 |
520 | // Log the last module results
521 | if ( config.currentModule ) {
522 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
523 | }
524 |
525 | var banner = id("qunit-banner"),
526 | tests = id("qunit-tests"),
527 | html = ['Tests completed in ',
528 | +new Date - config.started, ' milliseconds.
',
529 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join('');
530 |
531 | if ( banner ) {
532 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
533 | }
534 |
535 | if ( tests ) {
536 | var result = id("qunit-testresult");
537 |
538 | if ( !result ) {
539 | result = document.createElement("p");
540 | result.id = "qunit-testresult";
541 | result.className = "result";
542 | tests.parentNode.insertBefore( result, tests.nextSibling );
543 | }
544 |
545 | result.innerHTML = html;
546 | }
547 |
548 | QUnit.done( config.stats.bad, config.stats.all );
549 | }
550 |
551 | function validTest( name ) {
552 | var i = config.filters.length,
553 | run = false;
554 |
555 | if ( !i ) {
556 | return true;
557 | }
558 |
559 | while ( i-- ) {
560 | var filter = config.filters[i],
561 | not = filter.charAt(0) == '!';
562 |
563 | if ( not ) {
564 | filter = filter.slice(1);
565 | }
566 |
567 | if ( name.indexOf(filter) !== -1 ) {
568 | return !not;
569 | }
570 |
571 | if ( not ) {
572 | run = true;
573 | }
574 | }
575 |
576 | return run;
577 | }
578 |
579 | function push(result, actual, expected, message) {
580 | message = message || (result ? "okay" : "failed");
581 | QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) );
582 | }
583 |
584 | function synchronize( callback ) {
585 | config.queue.push( callback );
586 |
587 | if ( config.autorun && !config.blocking ) {
588 | process();
589 | }
590 | }
591 |
592 | function process() {
593 | while ( config.queue.length && !config.blocking ) {
594 | config.queue.shift()();
595 | }
596 | }
597 |
598 | function saveGlobal() {
599 | config.pollution = [];
600 |
601 | if ( config.noglobals ) {
602 | for ( var key in window ) {
603 | config.pollution.push( key );
604 | }
605 | }
606 | }
607 |
608 | function checkPollution( name ) {
609 | var old = config.pollution;
610 | saveGlobal();
611 |
612 | var newGlobals = diff( old, config.pollution );
613 | if ( newGlobals.length > 0 ) {
614 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
615 | config.expected++;
616 | }
617 |
618 | var deletedGlobals = diff( config.pollution, old );
619 | if ( deletedGlobals.length > 0 ) {
620 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
621 | config.expected++;
622 | }
623 | }
624 |
625 | // returns a new Array with the elements that are in a but not in b
626 | function diff( a, b ) {
627 | var result = a.slice();
628 | for ( var i = 0; i < result.length; i++ ) {
629 | for ( var j = 0; j < b.length; j++ ) {
630 | if ( result[i] === b[j] ) {
631 | result.splice(i, 1);
632 | i--;
633 | break;
634 | }
635 | }
636 | }
637 | return result;
638 | }
639 |
640 | function fail(message, exception, callback) {
641 | if ( typeof console !== "undefined" && console.error && console.warn ) {
642 | console.error(message);
643 | console.error(exception);
644 | console.warn(callback.toString());
645 |
646 | } else if ( window.opera && opera.postError ) {
647 | opera.postError(message, exception, callback.toString);
648 | }
649 | }
650 |
651 | function extend(a, b) {
652 | for ( var prop in b ) {
653 | a[prop] = b[prop];
654 | }
655 |
656 | return a;
657 | }
658 |
659 | function addEvent(elem, type, fn) {
660 | if ( elem.addEventListener ) {
661 | elem.addEventListener( type, fn, false );
662 | } else if ( elem.attachEvent ) {
663 | elem.attachEvent( "on" + type, fn );
664 | } else {
665 | fn();
666 | }
667 | }
668 |
669 | function id(name) {
670 | return !!(typeof document !== "undefined" && document && document.getElementById) &&
671 | document.getElementById( name );
672 | }
673 |
674 | // Test for equality any JavaScript type.
675 | // Discussions and reference: http://philrathe.com/articles/equiv
676 | // Test suites: http://philrathe.com/tests/equiv
677 | // Author: Philippe Rathé
678 | QUnit.equiv = function () {
679 |
680 | var innerEquiv; // the real equiv function
681 | var callers = []; // stack to decide between skip/abort functions
682 |
683 |
684 | // Determine what is o.
685 | function hoozit(o) {
686 | if (QUnit.is("String", o)) {
687 | return "string";
688 |
689 | } else if (QUnit.is("Boolean", o)) {
690 | return "boolean";
691 |
692 | } else if (QUnit.is("Number", o)) {
693 |
694 | if (isNaN(o)) {
695 | return "nan";
696 | } else {
697 | return "number";
698 | }
699 |
700 | } else if (typeof o === "undefined") {
701 | return "undefined";
702 |
703 | // consider: typeof null === object
704 | } else if (o === null) {
705 | return "null";
706 |
707 | // consider: typeof [] === object
708 | } else if (QUnit.is( "Array", o)) {
709 | return "array";
710 |
711 | // consider: typeof new Date() === object
712 | } else if (QUnit.is( "Date", o)) {
713 | return "date";
714 |
715 | // consider: /./ instanceof Object;
716 | // /./ instanceof RegExp;
717 | // typeof /./ === "function"; // => false in IE and Opera,
718 | // true in FF and Safari
719 | } else if (QUnit.is( "RegExp", o)) {
720 | return "regexp";
721 |
722 | } else if (typeof o === "object") {
723 | return "object";
724 |
725 | } else if (QUnit.is( "Function", o)) {
726 | return "function";
727 | } else {
728 | return undefined;
729 | }
730 | }
731 |
732 | // Call the o related callback with the given arguments.
733 | function bindCallbacks(o, callbacks, args) {
734 | var prop = hoozit(o);
735 | if (prop) {
736 | if (hoozit(callbacks[prop]) === "function") {
737 | return callbacks[prop].apply(callbacks, args);
738 | } else {
739 | return callbacks[prop]; // or undefined
740 | }
741 | }
742 | }
743 |
744 | var callbacks = function () {
745 |
746 | // for string, boolean, number and null
747 | function useStrictEquality(b, a) {
748 | if (b instanceof a.constructor || a instanceof b.constructor) {
749 | // to catch short annotaion VS 'new' annotation of a declaration
750 | // e.g. var i = 1;
751 | // var j = new Number(1);
752 | return a == b;
753 | } else {
754 | return a === b;
755 | }
756 | }
757 |
758 | return {
759 | "string": useStrictEquality,
760 | "boolean": useStrictEquality,
761 | "number": useStrictEquality,
762 | "null": useStrictEquality,
763 | "undefined": useStrictEquality,
764 |
765 | "nan": function (b) {
766 | return isNaN(b);
767 | },
768 |
769 | "date": function (b, a) {
770 | return hoozit(b) === "date" && a.valueOf() === b.valueOf();
771 | },
772 |
773 | "regexp": function (b, a) {
774 | return hoozit(b) === "regexp" &&
775 | a.source === b.source && // the regex itself
776 | a.global === b.global && // and its modifers (gmi) ...
777 | a.ignoreCase === b.ignoreCase &&
778 | a.multiline === b.multiline;
779 | },
780 |
781 | // - skip when the property is a method of an instance (OOP)
782 | // - abort otherwise,
783 | // initial === would have catch identical references anyway
784 | "function": function () {
785 | var caller = callers[callers.length - 1];
786 | return caller !== Object &&
787 | typeof caller !== "undefined";
788 | },
789 |
790 | "array": function (b, a) {
791 | var i;
792 | var len;
793 |
794 | // b could be an object literal here
795 | if ( ! (hoozit(b) === "array")) {
796 | return false;
797 | }
798 |
799 | len = a.length;
800 | if (len !== b.length) { // safe and faster
801 | return false;
802 | }
803 | for (i = 0; i < len; i++) {
804 | if ( ! innerEquiv(a[i], b[i])) {
805 | return false;
806 | }
807 | }
808 | return true;
809 | },
810 |
811 | "object": function (b, a) {
812 | var i;
813 | var eq = true; // unless we can proove it
814 | var aProperties = [], bProperties = []; // collection of strings
815 |
816 | // comparing constructors is more strict than using instanceof
817 | if ( a.constructor !== b.constructor) {
818 | return false;
819 | }
820 |
821 | // stack constructor before traversing properties
822 | callers.push(a.constructor);
823 |
824 | for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
825 |
826 | aProperties.push(i); // collect a's properties
827 |
828 | if ( ! innerEquiv(a[i], b[i])) {
829 | eq = false;
830 | }
831 | }
832 |
833 | callers.pop(); // unstack, we are done
834 |
835 | for (i in b) {
836 | bProperties.push(i); // collect b's properties
837 | }
838 |
839 | // Ensures identical properties name
840 | return eq && innerEquiv(aProperties.sort(), bProperties.sort());
841 | }
842 | };
843 | }();
844 |
845 | innerEquiv = function () { // can take multiple arguments
846 | var args = Array.prototype.slice.apply(arguments);
847 | if (args.length < 2) {
848 | return true; // end transition
849 | }
850 |
851 | return (function (a, b) {
852 | if (a === b) {
853 | return true; // catch the most you can
854 | } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) {
855 | return false; // don't lose time with error prone cases
856 | } else {
857 | return bindCallbacks(a, callbacks, [b, a]);
858 | }
859 |
860 | // apply transition with (1..n) arguments
861 | })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
862 | };
863 |
864 | return innerEquiv;
865 |
866 | }();
867 |
868 | /**
869 | * jsDump
870 | * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
871 | * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
872 | * Date: 5/15/2008
873 | * @projectDescription Advanced and extensible data dumping for Javascript.
874 | * @version 1.0.0
875 | * @author Ariel Flesler
876 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
877 | */
878 | QUnit.jsDump = (function() {
879 | function quote( str ) {
880 | return '"' + str.toString().replace(/"/g, '\\"') + '"';
881 | };
882 | function literal( o ) {
883 | return o + '';
884 | };
885 | function join( pre, arr, post ) {
886 | var s = jsDump.separator(),
887 | base = jsDump.indent(),
888 | inner = jsDump.indent(1);
889 | if ( arr.join )
890 | arr = arr.join( ',' + s + inner );
891 | if ( !arr )
892 | return pre + post;
893 | return [ pre, inner + arr, base + post ].join(s);
894 | };
895 | function array( arr ) {
896 | var i = arr.length, ret = Array(i);
897 | this.up();
898 | while ( i-- )
899 | ret[i] = this.parse( arr[i] );
900 | this.down();
901 | return join( '[', ret, ']' );
902 | };
903 |
904 | var reName = /^function (\w+)/;
905 |
906 | var jsDump = {
907 | parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
908 | var parser = this.parsers[ type || this.typeOf(obj) ];
909 | type = typeof parser;
910 |
911 | return type == 'function' ? parser.call( this, obj ) :
912 | type == 'string' ? parser :
913 | this.parsers.error;
914 | },
915 | typeOf:function( obj ) {
916 | var type;
917 | if ( obj === null ) {
918 | type = "null";
919 | } else if (typeof obj === "undefined") {
920 | type = "undefined";
921 | } else if (QUnit.is("RegExp", obj)) {
922 | type = "regexp";
923 | } else if (QUnit.is("Date", obj)) {
924 | type = "date";
925 | } else if (QUnit.is("Function", obj)) {
926 | type = "function";
927 | } else if (QUnit.is("Array", obj)) {
928 | type = "array";
929 | } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) {
930 | type = "window";
931 | } else if (QUnit.is("HTMLDocument", obj)) {
932 | type = "document";
933 | } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) {
934 | type = "nodelist";
935 | } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) {
936 | type = "node";
937 | } else {
938 | type = typeof obj;
939 | }
940 | return type;
941 | },
942 | separator:function() {
943 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
944 | },
945 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
946 | if ( !this.multiline )
947 | return '';
948 | var chr = this.indentChar;
949 | if ( this.HTML )
950 | chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
951 | return Array( this._depth_ + (extra||0) ).join(chr);
952 | },
953 | up:function( a ) {
954 | this._depth_ += a || 1;
955 | },
956 | down:function( a ) {
957 | this._depth_ -= a || 1;
958 | },
959 | setParser:function( name, parser ) {
960 | this.parsers[name] = parser;
961 | },
962 | // The next 3 are exposed so you can use them
963 | quote:quote,
964 | literal:literal,
965 | join:join,
966 | //
967 | _depth_: 1,
968 | // This is the list of parsers, to modify them, use jsDump.setParser
969 | parsers:{
970 | window: '[Window]',
971 | document: '[Document]',
972 | error:'[ERROR]', //when no parser is found, shouldn't happen
973 | unknown: '[Unknown]',
974 | 'null':'null',
975 | undefined:'undefined',
976 | 'function':function( fn ) {
977 | var ret = 'function',
978 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
979 | if ( name )
980 | ret += ' ' + name;
981 | ret += '(';
982 |
983 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
984 | return join( ret, this.parse(fn,'functionCode'), '}' );
985 | },
986 | array: array,
987 | nodelist: array,
988 | arguments: array,
989 | object:function( map ) {
990 | var ret = [ ];
991 | this.up();
992 | for ( var key in map )
993 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
994 | this.down();
995 | return join( '{', ret, '}' );
996 | },
997 | node:function( node ) {
998 | var open = this.HTML ? '<' : '<',
999 | close = this.HTML ? '>' : '>';
1000 |
1001 | var tag = node.nodeName.toLowerCase(),
1002 | ret = open + tag;
1003 |
1004 | for ( var a in this.DOMAttrs ) {
1005 | var val = node[this.DOMAttrs[a]];
1006 | if ( val )
1007 | ret += ' ' + a + '=' + this.parse( val, 'attribute' );
1008 | }
1009 | return ret + close + open + '/' + tag + close;
1010 | },
1011 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1012 | var l = fn.length;
1013 | if ( !l ) return '';
1014 |
1015 | var args = Array(l);
1016 | while ( l-- )
1017 | args[l] = String.fromCharCode(97+l);//97 is 'a'
1018 | return ' ' + args.join(', ') + ' ';
1019 | },
1020 | key:quote, //object calls it internally, the key part of an item in a map
1021 | functionCode:'[code]', //function calls it internally, it's the content of the function
1022 | attribute:quote, //node calls it internally, it's an html attribute value
1023 | string:quote,
1024 | date:quote,
1025 | regexp:literal, //regex
1026 | number:literal,
1027 | 'boolean':literal
1028 | },
1029 | DOMAttrs:{//attributes to dump from nodes, name=>realName
1030 | id:'id',
1031 | name:'name',
1032 | 'class':'className'
1033 | },
1034 | HTML:true,//if true, entities are escaped ( <, >, \t, space and \n )
1035 | indentChar:' ',//indentation unit
1036 | multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1037 | };
1038 |
1039 | return jsDump;
1040 | })();
1041 |
1042 | })(this);
1043 |
--------------------------------------------------------------------------------