Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
|
@ -1,16 +0,0 @@
|
|||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
npm-debug.log
|
||||
node_modules
|
||||
activemq-data
|
20
LICENSE
20
LICENSE
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Lookout
|
||||
|
||||
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:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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.
|
77
README.md
77
README.md
|
@ -1,77 +0,0 @@
|
|||
# activepush
|
||||
|
||||
Web push service built around ActiveMQ (or any STOMP broker).
|
||||
|
||||
ActivePush subscribes to a STOMP broker and relays messages with a specific `push_id` header to subscribed Socket.io clients. The message bodies are opaque to ActivePush, so the service is useful in a variety of applications.
|
||||
|
||||
## Usage
|
||||
|
||||
Production:
|
||||
|
||||
npm install
|
||||
bin/activepush [environment]
|
||||
|
||||
Development:
|
||||
|
||||
npm install -d
|
||||
bin/activepush
|
||||
|
||||
Or, to auto-reload on changes:
|
||||
|
||||
node-dev activepush.coffee
|
||||
|
||||
The optional `environment` argument corresponds to a configuration file in `configure`, which defaults to `development` and overrides values in the "default.yml" configuration file.
|
||||
|
||||
By default ActivePush expects a STOMP broker (e.x. ActiveMQ) to be running on `localhost:61613`.
|
||||
|
||||
If ActiveMQ is installed you can start it with the following command:
|
||||
|
||||
activemq start broker:stomp://localhost:61613
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration files are located in the `config` directory. `default.yml` is loaded first, and an environment-specific configuration (defaulting to `development.yml`) overrides the defaults.
|
||||
|
||||
The environment can be specified as the first argument the the `activepush` executable, or in the `NODE_ENV` environment variable.
|
||||
|
||||
## Production
|
||||
|
||||
All HTTP endpoints except those under the "/socket.io" URL should not be exposed externally as they could expose private information (e.g. "/health") or capabilities (e.g. "/send"). Alternatively, those endpoints could easily be moved to a separate port (see commented out code in the `start` method of ActivePush)
|
||||
|
||||
Multiple instances of ActivePush can be load-balanced, keeping the following in mind:
|
||||
|
||||
* A STOMP message bus rather than a queue should be used to ensure all instances recieve all messages
|
||||
* Socket.io needs clients to be "pinned" to the same backend across multiple requests (perhaps using cookies or the source IP address), in particular for the XHR polling transport.
|
||||
|
||||
## Architecture
|
||||
|
||||
The ActivePush class in `activepush.coffee` creates the server using the StompProducer and SocketIOConsumer. This could be made configurable, or even support multiple simultaneous producer/consumers.
|
||||
|
||||
The STOMP and WebSocket components are implemented in `consumers.coffee` and `producers.coffee`, respectively. Other producers/consumers could be implemented.
|
||||
|
||||
The producer/consumer components share a SubscriptionBroker `subscriptions` object, which is essentially an EventEmitter they can listen or emit `push_id` events to.
|
||||
|
||||
ActivePush makes extensive use of "promises" (specifically Q promises, which are compatible with the Promises/A+ standard) in both the application and tests.
|
||||
|
||||
## Testing
|
||||
|
||||
npm test
|
||||
|
||||
Or:
|
||||
|
||||
mocha --compilers coffee:coffee-script test
|
||||
|
||||
The integration tests assume a STOMP broker is running on `localhost:61613`. ActiveMQ can be started with the following command:
|
||||
|
||||
activemq start broker:stomp://localhost:61613
|
||||
|
||||
They also assume Selenium/WebDriver is running on `localhost:4444`. If you want to disable the WebDriver tests (they are significantly slower) comment out the last line of `test/integration-webdriver.coffee`, or run:
|
||||
|
||||
mocha --compilers coffee:coffee-script test/integration-socketio-client.coffee
|
||||
|
||||
`integration-common.coffee` implements the logic of the tests while `integration-socketio-client.coffee` and `integration-webdriver.coffee` implement a common API to create either an in-process `socket.io-client` or a remote WebDriver instance running the demo.html page (which stores messages it receives in `window.messages` for introspection by the test)
|
||||
|
||||
Unfortunately there is no easy way to tell when all messages have propagated from the running test to the ActiveMQ queue back to the ActivePush server and through the Socket.io client, so we currently have short delays before testing that messages have been received. This leads to occasional non-deterministic test failures. Increasing the delays reduces the frequency at a cost of longer running tests.
|
||||
## Contributors
|
||||
|
||||
* Tom Robinson <tlrobinson@gmail.com>
|
|
@ -1,118 +0,0 @@
|
|||
|
||||
Q = require "q"
|
||||
http = require "http"
|
||||
express = require "express"
|
||||
winston = require "winston"
|
||||
optimist = require "optimist"
|
||||
|
||||
{ SubscriptionBroker } = require "./lib/subscription-broker"
|
||||
{ SocketIOConsumer } = require "./lib/consumers"
|
||||
{ StompProducer } = require "./lib/producers"
|
||||
|
||||
config = exports.config = require "./lib/config"
|
||||
|
||||
class exports.ActivePush
|
||||
constructor: (config) ->
|
||||
@config = config
|
||||
|
||||
@logger = @config.logger or winston
|
||||
# Configure logging
|
||||
if @config.logging.file
|
||||
@logger.remove logger.transports.Console
|
||||
@logger.add logger.transports.File,
|
||||
filename: @config.logging.file
|
||||
@logger.level = (@config.logging.level or "info").toLowerCase()
|
||||
|
||||
start: ->
|
||||
@subscriptions = new SubscriptionBroker()
|
||||
|
||||
# Create STOMP producer
|
||||
@producer = new StompProducer
|
||||
logger: @logger
|
||||
subscriptions: @subscriptions
|
||||
inbox: @config.stomp.inbox
|
||||
host: @config.stomp.hosts[0].host
|
||||
port: @config.stomp.hosts[0].port
|
||||
|
||||
# Create Socket.io consumer
|
||||
@app = express()
|
||||
@server = http.createServer @app
|
||||
@consumer = new SocketIOConsumer @server,
|
||||
logger: @logger
|
||||
subscriptions: @subscriptions
|
||||
port: @config.http.port
|
||||
|
||||
@_initializePrivateEndpoints(@app)
|
||||
# Put these on another port by doing the following:
|
||||
# privateApp = express()
|
||||
# privateApp.listen(@config.http.port + 1)
|
||||
# @_initializePrivateEndpoints(privateApp)
|
||||
|
||||
Q.all([
|
||||
@consumer.start()
|
||||
@producer.start()
|
||||
]).then =>
|
||||
@
|
||||
|
||||
stop: ->
|
||||
Q.all([
|
||||
@consumer.stop()
|
||||
@producer.stop()
|
||||
])
|
||||
|
||||
_initializePrivateEndpoints: (app) ->
|
||||
# Health endpoint
|
||||
@app.get "/health", @_healthEndpoint
|
||||
|
||||
# Demo page and sending endpoint.
|
||||
@app.get "/", (req, res) ->
|
||||
res.sendfile "#{__dirname}/demo.html"
|
||||
@app.post "/send", express.json(), (req, res) =>
|
||||
@producer.publish req.body.push_id, req.body.message
|
||||
res.send 200
|
||||
|
||||
_healthEndpoint: (req, res) =>
|
||||
health =
|
||||
log:
|
||||
enabled: true
|
||||
level: @config.logging.level
|
||||
filename: @config.logging.file
|
||||
for component in [@subscriptions, @producer, @consumer]
|
||||
object = component.getHealth()
|
||||
health[object.name] = object
|
||||
delete object.name
|
||||
res.json health
|
||||
|
||||
exports.main = ->
|
||||
options = optimist
|
||||
.usage("Start ActivePush server.\nUsage: activepush [OPTIONS] [ENVIRONMENT]")
|
||||
.boolean(["h", "v"])
|
||||
.alias("c", "config")
|
||||
.describe("c", "Specify a configuration file")
|
||||
.alias("h", "help")
|
||||
.describe("h", "Show command line options and exit")
|
||||
.alias("v", "version")
|
||||
.describe("v", "Show version and exit")
|
||||
|
||||
if options.argv.help
|
||||
options.showHelp()
|
||||
process.exit()
|
||||
if options.argv.version
|
||||
console.log "v" + require("#{__dirname}/package.json").version
|
||||
process.exit()
|
||||
|
||||
configName = options.argv.config or options.argv._[0]
|
||||
|
||||
configuration = config.loadConfiguration(configName)
|
||||
activepush = new exports.ActivePush(configuration)
|
||||
activepush.start().then ->
|
||||
activepush.logger.info "Started with environment: #{activepush.config.environment}"
|
||||
process.on "SIGINT", ->
|
||||
activepush.logger.info "Shutting down..."
|
||||
activepush.stop().then ->
|
||||
process.exit(1)
|
||||
.done()
|
||||
.done()
|
||||
|
||||
if require.main is module
|
||||
exports.main()
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env coffee
|
||||
|
||||
require("../activepush").main()
|
|
@ -1,12 +0,0 @@
|
|||
#!/usr/bin/env coffee
|
||||
|
||||
config = require "../lib/config"
|
||||
{ StompProducer } = require "../lib/producers"
|
||||
|
||||
configuration = config.loadConfiguration process.argv[2]
|
||||
push_id = process.argv[3] or "demo"
|
||||
message = process.argv[4] or "hello world"
|
||||
|
||||
console.log { configuration, push_id, message }
|
||||
|
||||
StompProducer.publish configuration.stomp.hosts[0], configuration.stomp.inbox, push_id, message
|
|
@ -1,15 +0,0 @@
|
|||
stomp:
|
||||
inbox: "/topic/activepush"
|
||||
hosts:
|
||||
- host: "localhost"
|
||||
port: 61613
|
||||
ssl: false
|
||||
login: "guest"
|
||||
passcode: "guest"
|
||||
|
||||
logging:
|
||||
level: "WARN"
|
||||
file: "log/activepush.log"
|
||||
|
||||
http:
|
||||
port: 9191
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
logging:
|
||||
level: "DEBUG"
|
||||
file: null
|
|
@ -1,5 +0,0 @@
|
|||
http:
|
||||
port: 6789
|
||||
|
||||
logging:
|
||||
file: null
|
79
demo.html
79
demo.html
|
@ -1,79 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>ActivePush Demo Page</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div>
|
||||
<label for="push-id">push id:</label>
|
||||
<input type="text" name="push-id" id="push-id" value="">
|
||||
<label for="message">message:</label>
|
||||
<input type="text" name="message" id="message" value="hello world">
|
||||
<input type="submit" id="send" value="Send">
|
||||
</div>
|
||||
<hr>
|
||||
<pre id="log"></pre>
|
||||
<hr>
|
||||
<pre id="health"></pre>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
var push_id = window.location.hash.slice(1) || "demo"
|
||||
window.location.hash = "#" + push_id;
|
||||
|
||||
// Sample client
|
||||
var socket = io.connect("http://localhost");
|
||||
socket.on("connect", function () {
|
||||
console.log("connected");
|
||||
socket.emit("subscribe", push_id);
|
||||
console.log("subscribed to push_id:", push_id);
|
||||
|
||||
// Used in integration tests:
|
||||
window.messages = [];
|
||||
});
|
||||
socket.on("disconnect", function () {
|
||||
console.log("disconnected");
|
||||
});
|
||||
socket.on("error", function (error) {
|
||||
console.log("error", error);
|
||||
});
|
||||
socket.on("message", function (data) {
|
||||
messages.push(data)
|
||||
console.log("message:", JSON.stringify(data, null, 2));
|
||||
});
|
||||
|
||||
// Demo form
|
||||
document.getElementById("push-id").value = push_id;
|
||||
document.getElementById("send").onclick = function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "send");
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({
|
||||
push_id: document.getElementById("push-id").value,
|
||||
message: document.getElementById("message").value
|
||||
}));
|
||||
}
|
||||
|
||||
// Auto-refresh /health
|
||||
health = document.getElementById("health");
|
||||
setInterval(function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/health");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
health.innerText = xhr.responseText;
|
||||
}
|
||||
}
|
||||
xhr.send();
|
||||
}, 1000)
|
||||
|
||||
// Also pipe log to page for demo purposes
|
||||
console._log = console.log;
|
||||
console.log = function() {
|
||||
console._log.apply(console, arguments);
|
||||
document.getElementById("log").textContent += new Date() + ": " + Array.prototype.join.call(arguments, " ") + "\n";
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,154 @@
|
|||
<!doctype html>
|
||||
<!-- The Time Machine GitHub pages theme was designed and developed by Jon Rohan, on Feb 7, 2012. -->
|
||||
<!-- Follow him for fun. http://twitter.com/jonrohan. Tail his code on http://github.com/jonrohan -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/stylesheet.css" media="screen"/>
|
||||
<link rel="stylesheet" href="stylesheets/pygment_trac.css"/>
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="javascripts/script.js"></script>
|
||||
|
||||
<title>Activepush</title>
|
||||
<meta name="description" content="A socket.io push service built around ActiveMQ">
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1 class="title">Activepush</h1>
|
||||
</header>
|
||||
<div id="container">
|
||||
<p class="tagline">A socket.io push service built around ActiveMQ</p>
|
||||
<div id="main" role="main">
|
||||
<div class="download-bar">
|
||||
<div class="inner">
|
||||
<a href="https://github.com/lookout/activepush/tarball/master" class="download-button tar"><span>Download</span></a>
|
||||
<a href="https://github.com/lookout/activepush/zipball/master" class="download-button zip"><span>Download</span></a>
|
||||
<a href="https://github.com/lookout/activepush" class="code">View Activepush on GitHub</a>
|
||||
</div>
|
||||
<span class="blc"></span><span class="trc"></span>
|
||||
</div>
|
||||
<article class="markdown-body">
|
||||
<p>Web push service built around ActiveMQ (or any STOMP broker).</p>
|
||||
|
||||
<p>ActivePush subscribes to a STOMP broker and relays messages with a specific <code>push_id</code> header to subscribed Socket.io clients. The message bodies are opaque to ActivePush, so the service is useful in a variety of applications.</p>
|
||||
|
||||
<h2>
|
||||
<a name="usage" class="anchor" href="#usage"><span class="octicon octicon-link"></span></a>Usage</h2>
|
||||
|
||||
<p>Production:</p>
|
||||
|
||||
<pre><code>npm install
|
||||
bin/activepush [environment]
|
||||
</code></pre>
|
||||
|
||||
<p>Development:</p>
|
||||
|
||||
<pre><code>npm install -d
|
||||
bin/activepush
|
||||
</code></pre>
|
||||
|
||||
<p>Or, to auto-reload on changes:</p>
|
||||
|
||||
<pre><code>node-dev activepush.coffee
|
||||
</code></pre>
|
||||
|
||||
<p>The optional <code>environment</code> argument corresponds to a configuration file in <code>configure</code>, which defaults to <code>development</code> and overrides values in the "default.yml" configuration file.</p>
|
||||
|
||||
<p>By default ActivePush expects a STOMP broker (e.x. ActiveMQ) to be running on <code>localhost:61613</code>.</p>
|
||||
|
||||
<p>If ActiveMQ is installed you can start it with the following command:</p>
|
||||
|
||||
<pre><code>activemq start broker:stomp://localhost:61613
|
||||
</code></pre>
|
||||
|
||||
<h2>
|
||||
<a name="configuration" class="anchor" href="#configuration"><span class="octicon octicon-link"></span></a>Configuration</h2>
|
||||
|
||||
<p>Configuration files are located in the <code>config</code> directory. <code>default.yml</code> is loaded first, and an environment-specific configuration (defaulting to <code>development.yml</code>) overrides the defaults.</p>
|
||||
|
||||
<p>The environment can be specified as the first argument the the <code>activepush</code> executable, or in the <code>NODE_ENV</code> environment variable.</p>
|
||||
|
||||
<h2>
|
||||
<a name="production" class="anchor" href="#production"><span class="octicon octicon-link"></span></a>Production</h2>
|
||||
|
||||
<p>All HTTP endpoints except those under the "/socket.io" URL should not be exposed externally as they could expose private information (e.g. "/health") or capabilities (e.g. "/send"). Alternatively, those endpoints could easily be moved to a separate port (see commented out code in the <code>start</code> method of ActivePush)</p>
|
||||
|
||||
<p>Multiple instances of ActivePush can be load-balanced, keeping the following in mind:</p>
|
||||
|
||||
<ul>
|
||||
<li>A STOMP message bus rather than a queue should be used to ensure all instances recieve all messages</li>
|
||||
<li>Socket.io needs clients to be "pinned" to the same backend across multiple requests (perhaps using cookies or the source IP address), in particular for the XHR polling transport.</li>
|
||||
</ul><h2>
|
||||
<a name="architecture" class="anchor" href="#architecture"><span class="octicon octicon-link"></span></a>Architecture</h2>
|
||||
|
||||
<p>The ActivePush class in <code>activepush.coffee</code> creates the server using the StompProducer and SocketIOConsumer. This could be made configurable, or even support multiple simultaneous producer/consumers.</p>
|
||||
|
||||
<p>The STOMP and WebSocket components are implemented in <code>consumers.coffee</code> and <code>producers.coffee</code>, respectively. Other producers/consumers could be implemented.</p>
|
||||
|
||||
<p>The producer/consumer components share a SubscriptionBroker <code>subscriptions</code> object, which is essentially an EventEmitter they can listen or emit <code>push_id</code> events to.</p>
|
||||
|
||||
<p>ActivePush makes extensive use of "promises" (specifically Q promises, which are compatible with the Promises/A+ standard) in both the application and tests.</p>
|
||||
|
||||
<h2>
|
||||
<a name="testing" class="anchor" href="#testing"><span class="octicon octicon-link"></span></a>Testing</h2>
|
||||
|
||||
<pre><code>npm test
|
||||
</code></pre>
|
||||
|
||||
<p>Or:</p>
|
||||
|
||||
<pre><code>mocha --compilers coffee:coffee-script test
|
||||
</code></pre>
|
||||
|
||||
<p>The integration tests assume a STOMP broker is running on <code>localhost:61613</code>. ActiveMQ can be started with the following command:</p>
|
||||
|
||||
<pre><code>activemq start broker:stomp://localhost:61613
|
||||
</code></pre>
|
||||
|
||||
<p>They also assume Selenium/WebDriver is running on <code>localhost:4444</code>. If you want to disable the WebDriver tests (they are significantly slower) comment out the last line of <code>test/integration-webdriver.coffee</code>, or run:</p>
|
||||
|
||||
<pre><code>mocha --compilers coffee:coffee-script test/integration-socketio-client.coffee
|
||||
</code></pre>
|
||||
|
||||
<p><code>integration-common.coffee</code> implements the logic of the tests while <code>integration-socketio-client.coffee</code> and <code>integration-webdriver.coffee</code> implement a common API to create either an in-process <code>socket.io-client</code> or a remote WebDriver instance running the demo.html page (which stores messages it receives in <code>window.messages</code> for introspection by the test)</p>
|
||||
|
||||
<p>Unfortunately there is no easy way to tell when all messages have propagated from the running test to the ActiveMQ queue back to the ActivePush server and through the Socket.io client, so we currently have short delays before testing that messages have been received. This leads to occasional non-deterministic test failures. Increasing the delays reduces the frequency at a cost of longer running tests.</p>
|
||||
|
||||
<h2>
|
||||
<a name="contributors" class="anchor" href="#contributors"><span class="octicon octicon-link"></span></a>Contributors</h2>
|
||||
|
||||
<ul>
|
||||
<li>Tom Robinson <a href="mailto:tlrobinson@gmail.com">tlrobinson@gmail.com</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="owner">
|
||||
<p><a href="https://github.com/lookout" class="avatar"><img src="https://secure.gravatar.com/avatar/ee9f2b5fc43be14aedb09b84b30c5a2c?s=30&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png" width="48" height="48"/></a> <a href="https://github.com/lookout">lookout</a> maintains <a href="https://github.com/lookout/activepush">Activepush</a></p>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="creds">
|
||||
<small>This page generated using <a href="https://pages.github.com/">GitHub Pages</a><br/>theme by <a href="https://twitter.com/jonrohan/">Jon Rohan</a></small>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="current-section">
|
||||
<a href="#top">Scroll to top</a>
|
||||
<a href="https://github.com/lookout/activepush/tarball/master" class="tar">tar</a><a href="https://github.com/lookout/activepush/zipball/master" class="zip">zip</a><a href="" class="code">source code</a>
|
||||
<p class="name"></p>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
(function($) {
|
||||
$(document).ready(function(){
|
||||
|
||||
// putting lines by the pre blocks
|
||||
$("pre").each(function(){
|
||||
var pre = $(this).text().split("\n");
|
||||
var lines = new Array(pre.length+1);
|
||||
for(var i = 0; i < pre.length; i++) {
|
||||
var wrap = Math.floor(pre[i].split("").length / 70)
|
||||
if (pre[i]==""&&i==pre.length-1) {
|
||||
lines.splice(i, 1);
|
||||
} else {
|
||||
lines[i] = i+1;
|
||||
for(var j = 0; j < wrap; j++) {
|
||||
lines[i] += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$(this).before("<pre class='lines'>" + lines.join("\n") + "</pre>");
|
||||
});
|
||||
|
||||
var headings = [];
|
||||
|
||||
var collectHeaders = function(){
|
||||
headings.push({"top":$(this).offset().top - 15,"text":$(this).text()});
|
||||
}
|
||||
|
||||
if($(".markdown-body h1").length > 1) $(".markdown-body h1").each(collectHeaders)
|
||||
else if($(".markdown-body h2").length > 1) $(".markdown-body h2").each(collectHeaders)
|
||||
else if($(".markdown-body h3").length > 1) $(".markdown-body h3").each(collectHeaders)
|
||||
|
||||
$(window).scroll(function(){
|
||||
if(headings.length==0) return true;
|
||||
var scrolltop = $(window).scrollTop() || 0;
|
||||
if(headings[0] && scrolltop < headings[0].top) {
|
||||
$(".current-section").css({"opacity":0,"visibility":"hidden"});
|
||||
return false;
|
||||
}
|
||||
$(".current-section").css({"opacity":1,"visibility":"visible"});
|
||||
for(var i in headings) {
|
||||
if(scrolltop >= headings[i].top) {
|
||||
$(".current-section .name").text(headings[i].text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".current-section a").click(function(){
|
||||
$(window).scrollTop(0);
|
||||
return false;
|
||||
})
|
||||
});
|
||||
})(jQuery)
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
{ EventEmitter } = require "events"
|
||||
|
||||
class exports.Module extends EventEmitter
|
||||
constructor: (options) ->
|
||||
EventEmitter.call @
|
||||
@options = options
|
||||
@logger = options.logger or nullLogger
|
||||
@subscriptions = options.subscriptions
|
||||
|
||||
|
||||
nullLogger = {}
|
||||
nullLogger.error = nullLogger.warn = nullLogger.info = nullLogger.debug = (->)
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
require "js-yaml"
|
||||
path = require "path"
|
||||
merge = require "deepmerge"
|
||||
|
||||
# If name matches ^\w+$ then use one of the built-in configurations,
|
||||
# otherwise assume it's a path.
|
||||
exports.loadConfiguration = (name) ->
|
||||
name = name or process.env["NODE_ENV"] or "development"
|
||||
if /^\w+$/.test(name)
|
||||
config = path.resolve "#{__dirname}/../config/#{name}.yml"
|
||||
else
|
||||
config = path.resolve ".", name
|
||||
deflt = require("#{__dirname}/../config/default.yml") or {}
|
||||
overlay = require(config) or {}
|
||||
merge(merge(deflt, overlay), environment: name)
|
|
@ -1,43 +0,0 @@
|
|||
|
||||
Q = require "q"
|
||||
|
||||
Consumer = require("./common").Module
|
||||
|
||||
socket_io = require "socket.io"
|
||||
class SocketIOConsumer extends Consumer
|
||||
constructor: (server, options = {}) ->
|
||||
Consumer.call @, options
|
||||
@server = server
|
||||
|
||||
start: ->
|
||||
@server = require("http").createServer() unless @server?
|
||||
|
||||
@io = socket_io.listen @server, log: false, transports: ["websocket", "xhr-polling"]
|
||||
@io.sockets.on "connection", @_onConnection
|
||||
|
||||
Q.ninvoke(@server, "listen", @options.port).then =>
|
||||
@logger.info "SOCKET.IO listening on port %s", @options.port
|
||||
|
||||
stop: ->
|
||||
Q.try =>
|
||||
@server.close()
|
||||
|
||||
_onConnection: (socket) =>
|
||||
@logger.debug "SOCKET.IO connection"
|
||||
socket.on "error", (error) =>
|
||||
@logger.warn "SOCKET.IO error: %s", error
|
||||
socket.on "subscribe", (push_id) =>
|
||||
@logger.info "SOCKET.IO subscribed: %s", push_id
|
||||
listener = (message) =>
|
||||
@logger.debug "SOCKET.IO send push_id=%s message=%s", push_id, message
|
||||
socket.send message
|
||||
@subscriptions.addListener push_id, listener
|
||||
socket.on "disconnect", =>
|
||||
@logger.info "SOCKET.IO disconnected: %s", push_id
|
||||
@subscriptions.removeListener push_id, listener
|
||||
|
||||
getHealth: ->
|
||||
name: "socketio"
|
||||
clients: @io.sockets.clients().length
|
||||
|
||||
module.exports = { Consumer, SocketIOConsumer }
|
|
@ -1,116 +0,0 @@
|
|||
|
||||
Q = require "q"
|
||||
merge = require "deepmerge"
|
||||
|
||||
Producer = require("./common").Module
|
||||
|
||||
class StompProducer extends Producer
|
||||
constructor: (options) ->
|
||||
Producer.call @, options
|
||||
|
||||
getHealth: ->
|
||||
name: "stomp"
|
||||
host: @options.host
|
||||
port: @options.port
|
||||
inbox: @options.inbox
|
||||
|
||||
{ Stomp } = require "stomp"
|
||||
{ ReconnectingStomp } = require "./reconnecting-stomp"
|
||||
|
||||
# Library #1: stomp
|
||||
class NodeStompProducer extends StompProducer
|
||||
|
||||
start: ->
|
||||
deferred = Q.defer()
|
||||
options = merge(@options.host, debug: false)
|
||||
# @stomp = new Stomp options
|
||||
@stomp = new ReconnectingStomp options
|
||||
@stomp.connect()
|
||||
@stomp.on "connected", =>
|
||||
@_onConnected()
|
||||
deferred.resolve()
|
||||
@stomp.on "disconnected", =>
|
||||
@logger.error "STOMP disconnected: %s:%s", @options.host, @options.port
|
||||
deferred.reject()
|
||||
@stomp.on "error", (error) =>
|
||||
@logger.error "STOMP error: %s", error
|
||||
deferred.promise
|
||||
|
||||
stop: ->
|
||||
Q.try =>
|
||||
@stomp.disconnect()
|
||||
|
||||
_onConnected: =>
|
||||
@logger.info "STOMP connected: %s:%s%s", @options.host, @options.port, @options.inbox
|
||||
@stomp.subscribe { destination: @options.inbox, ack: "client" }, @_onMessage
|
||||
|
||||
_onMessage: (body, headers) =>
|
||||
push_id = headers.push_id
|
||||
message = body[0]
|
||||
@logger.debug "STOMP receive push_id=%s message=%s", push_id, message
|
||||
@subscriptions.emit push_id, message
|
||||
|
||||
publish: (push_id, message) ->
|
||||
@stomp.send
|
||||
destination: @options.inbox
|
||||
push_id: push_id
|
||||
body: message
|
||||
persistent: false
|
||||
, false
|
||||
|
||||
NodeStompProducer.publish = (options, push_id, message) ->
|
||||
console.log "options", options
|
||||
stomp = new NodeStompProducer options
|
||||
stomp.start().then ->
|
||||
stomp.publish(push_id, message)
|
||||
stomp.stop()
|
||||
|
||||
StompProducer = NodeStompProducer
|
||||
|
||||
# Library #2: stompit
|
||||
# stompit = require "stompit"
|
||||
# class StompitProducer extends StompProducer
|
||||
|
||||
# start: ->
|
||||
# deferred = Q.defer()
|
||||
# @stomp = stompit.connect host: @options.host, port: @options.port, =>
|
||||
# @_onConnected()
|
||||
# deferred.resolve()
|
||||
# deferred.promise
|
||||
|
||||
# stop: ->
|
||||
# Q.try =>
|
||||
# @stomp.disconnect()
|
||||
|
||||
# _onConnected: =>
|
||||
# @stomp.subscribe { destination: @options.inbox }, @_onMessage
|
||||
# @logger.info "STOMP connected: %s:%s%s", @options.host, @options.port, @options.inbox
|
||||
|
||||
# _onMessage: (message) =>
|
||||
# body = ""
|
||||
# message.on "data", (data) ->
|
||||
# body += data.toString("utf-8")
|
||||
# message.on "end", =>
|
||||
# push_id = message.headers.push_id
|
||||
# @logger.debug "STOMP receive push_id=%s message=%s", push_id, body
|
||||
# @subscriptions.emit push_id, body
|
||||
|
||||
# publish: (push_id, message) ->
|
||||
# frame = @stomp.send(
|
||||
# destination: @options.inbox
|
||||
# push_id: push_id
|
||||
# persistent: false
|
||||
# )
|
||||
# Q.ninvoke(frame, "end", message)
|
||||
|
||||
# StompitProducer.publish = (options, push_id, message) ->
|
||||
# console.log "options", options
|
||||
# stomp = new StompitProducer options
|
||||
# stomp.start().then ->
|
||||
# stomp.publish(push_id, message)
|
||||
# .then ->
|
||||
# stomp.stop()
|
||||
|
||||
# StompProducer = StompitProducer
|
||||
|
||||
module.exports = { Producer, StompProducer }
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
{ Stomp } = require "stomp"
|
||||
|
||||
# Subclass of Stomp that automatically tries to reconnect, similar options to Ruby STOMP gem
|
||||
class exports.ReconnectingStomp extends Stomp
|
||||
constructor: (args) ->
|
||||
Stomp.apply @, arguments
|
||||
|
||||
@initial_reconnect_delay = args.initial_reconnect_delay or 1
|
||||
@max_reconnect_delay = args.max_reconnect_delay or 30.0
|
||||
@use_exponential_back_off = if args.use_exponential_back_off? then args.use_exponential_back_off else true
|
||||
@back_off_multiplier = args.back_off_multiplier or 2
|
||||
@max_reconnect_attempts = args.max_reconnect_attempts or 0
|
||||
|
||||
@_resetReconnection()
|
||||
@on "connected", @_resetReconnection
|
||||
|
||||
# Hook emit to intercept "disconnect" error.
|
||||
# Kind of hacky. Should we wrap the class instead?
|
||||
emit: (name, object) ->
|
||||
if name is "disconnected"
|
||||
unless @max_reconnect_attempts > 0 and @reconnectCount >= @max_reconnect_attempts
|
||||
@_reconnect()
|
||||
return
|
||||
Stomp::emit.apply @, arguments
|
||||
|
||||
_reconnect: =>
|
||||
if @reconnectTimer?
|
||||
return
|
||||
if @use_exponential_back_off
|
||||
@reconnectDelay = Math.min(@max_reconnect_delay * 1000, @reconnectDelay * @back_off_multiplier)
|
||||
@reconnectTimer = setTimeout =>
|
||||
@max_reconnect_attempts++
|
||||
@connect()
|
||||
delete @reconnectTimer
|
||||
, @reconnectDelay
|
||||
|
||||
_resetReconnection: =>
|
||||
@reconnectCount = 0
|
||||
@reconnectTimer = null
|
||||
@reconnectDelay = @initial_reconnect_delay * 1000
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
{ EventEmitter } = require "events"
|
||||
|
||||
class exports.SubscriptionBroker extends EventEmitter
|
||||
constructor: ->
|
||||
EventEmitter.call @
|
||||
@messageCount = 0
|
||||
|
||||
emit: ->
|
||||
@messageCount++
|
||||
EventEmitter::emit.apply @, arguments
|
||||
|
||||
getHealth: ->
|
||||
health =
|
||||
name: "subscriptions"
|
||||
push_ids: {}
|
||||
subscriptions: 0
|
||||
messages: @messageCount
|
||||
for name, value of @_events
|
||||
count = if Array.isArray(value) then value.length else 1
|
||||
health.push_ids[name] = count
|
||||
health.total += count
|
||||
health
|
42
package.json
42
package.json
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"name": "activepush",
|
||||
"version": "1.0.3",
|
||||
"description": "Web push service built around ActiveMQ (or any STOMP broker)",
|
||||
"main": "activepush.coffee",
|
||||
"bin": {
|
||||
"activepush": "./bin/activepush",
|
||||
"activepush-client-example": "./bin/example-client"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node_modules/.bin/coffee activepush.coffee",
|
||||
"test": "node_modules/.bin/mocha --compilers coffee:coffee-script test"
|
||||
},
|
||||
"repository": "git://github.com:lookout/activepush.git",
|
||||
"author": "Lookout (https://www.lookout.com/)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"q": "~0.9.6",
|
||||
"coffee-script": "~1.6.3",
|
||||
"socket.io": "~0.9.16",
|
||||
"stomp": "~0.1.1",
|
||||
"js-yaml": "~2.1.0",
|
||||
"express": "~3.3.4",
|
||||
"deepmerge": "~0.2.7",
|
||||
"winston": "~0.7.2",
|
||||
"stompit": "~0.9.0",
|
||||
"optimist": "~0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "~1.12.0",
|
||||
"mocha-as-promised": "~1.4.0",
|
||||
"chai": "~1.7.2",
|
||||
"chai-as-promised": "~3.3.1",
|
||||
"q": "~0.9.6",
|
||||
"q-step": "0.0.1",
|
||||
"q-proxy": "0.0.1",
|
||||
"node-uuid": "~1.4.0",
|
||||
"socket.io-client": "~0.9.16",
|
||||
"wd": "0.0.33",
|
||||
"node-dev": "latest"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"name":"Activepush","tagline":"A socket.io push service built around ActiveMQ","body":"Web push service built around ActiveMQ (or any STOMP broker).\r\n\r\nActivePush subscribes to a STOMP broker and relays messages with a specific `push_id` header to subscribed Socket.io clients. The message bodies are opaque to ActivePush, so the service is useful in a variety of applications.\r\n\r\n## Usage\r\n\r\nProduction:\r\n\r\n npm install\r\n bin/activepush [environment]\r\n\r\nDevelopment:\r\n\r\n npm install -d\r\n bin/activepush\r\n\r\nOr, to auto-reload on changes:\r\n\r\n node-dev activepush.coffee\r\n\r\nThe optional `environment` argument corresponds to a configuration file in `configure`, which defaults to `development` and overrides values in the \"default.yml\" configuration file.\r\n\r\nBy default ActivePush expects a STOMP broker (e.x. ActiveMQ) to be running on `localhost:61613`.\r\n\r\nIf ActiveMQ is installed you can start it with the following command:\r\n\r\n activemq start broker:stomp://localhost:61613\r\n\r\n## Configuration\r\n\r\nConfiguration files are located in the `config` directory. `default.yml` is loaded first, and an environment-specific configuration (defaulting to `development.yml`) overrides the defaults.\r\n\r\nThe environment can be specified as the first argument the the `activepush` executable, or in the `NODE_ENV` environment variable.\r\n\r\n## Production\r\n\r\nAll HTTP endpoints except those under the \"/socket.io\" URL should not be exposed externally as they could expose private information (e.g. \"/health\") or capabilities (e.g. \"/send\"). Alternatively, those endpoints could easily be moved to a separate port (see commented out code in the `start` method of ActivePush)\r\n\r\nMultiple instances of ActivePush can be load-balanced, keeping the following in mind:\r\n\r\n* A STOMP message bus rather than a queue should be used to ensure all instances recieve all messages\r\n* Socket.io needs clients to be \"pinned\" to the same backend across multiple requests (perhaps using cookies or the source IP address), in particular for the XHR polling transport.\r\n\r\n## Architecture\r\n\r\nThe ActivePush class in `activepush.coffee` creates the server using the StompProducer and SocketIOConsumer. This could be made configurable, or even support multiple simultaneous producer/consumers.\r\n\r\nThe STOMP and WebSocket components are implemented in `consumers.coffee` and `producers.coffee`, respectively. Other producers/consumers could be implemented.\r\n\r\nThe producer/consumer components share a SubscriptionBroker `subscriptions` object, which is essentially an EventEmitter they can listen or emit `push_id` events to.\r\n\r\nActivePush makes extensive use of \"promises\" (specifically Q promises, which are compatible with the Promises/A+ standard) in both the application and tests.\r\n\r\n## Testing\r\n\r\n npm test\r\n\r\nOr:\r\n\r\n mocha --compilers coffee:coffee-script test\r\n\r\nThe integration tests assume a STOMP broker is running on `localhost:61613`. ActiveMQ can be started with the following command:\r\n\r\n activemq start broker:stomp://localhost:61613\r\n\r\nThey also assume Selenium/WebDriver is running on `localhost:4444`. If you want to disable the WebDriver tests (they are significantly slower) comment out the last line of `test/integration-webdriver.coffee`, or run:\r\n\r\n mocha --compilers coffee:coffee-script test/integration-socketio-client.coffee\r\n\r\n`integration-common.coffee` implements the logic of the tests while `integration-socketio-client.coffee` and `integration-webdriver.coffee` implement a common API to create either an in-process `socket.io-client` or a remote WebDriver instance running the demo.html page (which stores messages it receives in `window.messages` for introspection by the test)\r\n\r\nUnfortunately there is no easy way to tell when all messages have propagated from the running test to the ActiveMQ queue back to the ActivePush server and through the Socket.io client, so we currently have short delays before testing that messages have been received. This leads to occasional non-deterministic test failures. Increasing the delays reduces the frequency at a cost of longer running tests.\r\n## Contributors\r\n\r\n* Tom Robinson <tlrobinson@gmail.com>\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}
|
|
@ -0,0 +1,69 @@
|
|||
.highlight { background: #ffffff; }
|
||||
.highlight .c { color: #999988; font-style: italic } /* Comment */
|
||||
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||
.highlight .k { font-weight: bold } /* Keyword */
|
||||
.highlight .o { font-weight: bold } /* Operator */
|
||||
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
|
||||
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #aa0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #999999 } /* Generic.Heading */
|
||||
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
|
||||
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
|
||||
.highlight .go { color: #888888 } /* Generic.Output */
|
||||
.highlight .gp { color: #555555 } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
|
||||
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
|
||||
.highlight .kc { font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
|
||||
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .m { color: #009999 } /* Literal.Number */
|
||||
.highlight .s { color: #d14 } /* Literal.String */
|
||||
.highlight .na { color: #008080 } /* Name.Attribute */
|
||||
.highlight .nb { color: #0086B3 } /* Name.Builtin */
|
||||
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #008080 } /* Name.Constant */
|
||||
.highlight .ni { color: #800080 } /* Name.Entity */
|
||||
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
|
||||
.highlight .nn { color: #555555 } /* Name.Namespace */
|
||||
.highlight .nt { color: #000080 } /* Name.Tag */
|
||||
.highlight .nv { color: #008080 } /* Name.Variable */
|
||||
.highlight .ow { font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #009999 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #d14 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #d14 } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #d14 } /* Literal.String.Double */
|
||||
.highlight .se { color: #d14 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #d14 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #d14 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #009926 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #d14 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #008080 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #008080 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
|
||||
|
||||
.type-csharp .highlight .k { color: #0000FF }
|
||||
.type-csharp .highlight .kt { color: #0000FF }
|
||||
.type-csharp .highlight .nf { color: #000000; font-weight: normal }
|
||||
.type-csharp .highlight .nc { color: #2B91AF }
|
||||
.type-csharp .highlight .nn { color: #000000 }
|
||||
.type-csharp .highlight .s { color: #A31515 }
|
||||
.type-csharp .highlight .sc { color: #A31515 }
|
|
@ -0,0 +1,581 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Style */
|
||||
|
||||
body {
|
||||
font-size: 15px;
|
||||
font-family: Arial, Arial, Helvetica, sans-serif;
|
||||
line-height: 1.5;
|
||||
background: #D1D1D1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #63a52a;
|
||||
text-decoration: none;
|
||||
-webkit-transition: color ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: #90D355;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
margin: 30px 20px 10px;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
font-family:Georgia, serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 675px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#container {
|
||||
border: 1px solid #2a2a2a;
|
||||
background: #ddd url(../images/pattern.png);
|
||||
box-shadow: 0 0 5px #b1b1b1;
|
||||
}
|
||||
|
||||
p.tagline {
|
||||
padding: 20px 20px 0;
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin-top: 20px;
|
||||
padding: 0 20px 90px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.download-bar {
|
||||
background: #222;
|
||||
border: 5px solid #444;
|
||||
padding: 10px;
|
||||
margin: 0 -35px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.download-bar .inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.download-bar .watch-fork iframe {
|
||||
display: block;
|
||||
float: left;
|
||||
border-right: 1px solid #ddd;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.download-bar .watch-fork iframe.last {
|
||||
border-right: 0 none;
|
||||
padding-right: 0;
|
||||
padding-left: 5px;
|
||||
border-left: 1px solid #fff;
|
||||
}
|
||||
.download-bar .watch-fork {
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
background-color: #eee;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.download-bar .blc {
|
||||
border: 10px solid black;
|
||||
border-color: transparent transparent black;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
left: 0;
|
||||
-moz-transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.download-bar .trc {
|
||||
border: 10px solid black;
|
||||
border-color: black transparent transparent;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
right: 0;
|
||||
-moz-transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.download-bar .avatar {
|
||||
border: 1px solid black;
|
||||
display: block;
|
||||
padding: 4px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.download-bar .avatar img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.download-bar a.code {
|
||||
background: transparent url(../images/code.png) no-repeat 0 2px;
|
||||
padding-left: 35px;
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
float: left;
|
||||
text-indent: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
opacity: 1;
|
||||
-moz-opacity: 1;
|
||||
filter:alpha(opacity=1);
|
||||
}
|
||||
|
||||
.current-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 693px;
|
||||
margin-left: -352px;
|
||||
background: #222;
|
||||
border: 5px solid #444;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
.current-section p {
|
||||
padding: 5px 27px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.current-section a {
|
||||
float: right;
|
||||
text-indent: -10000px;
|
||||
background: transparent url(../images/top.png) no-repeat 0 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0.8;
|
||||
margin-right: 12px;
|
||||
margin-top: 12px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
.current-section a:hover {
|
||||
opacity: 1;
|
||||
-moz-opacity: 1;
|
||||
filter:alpha(opacity=1);
|
||||
}
|
||||
|
||||
.current-section a.zip {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
a.zip,
|
||||
a.zip span {
|
||||
background: transparent url(../images/zip.png) no-repeat 0 0;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
text-indent: -10000px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a.tar,
|
||||
a.tar span {
|
||||
background: transparent url(../images/tar.png) no-repeat 0 0;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
text-indent: -10000px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a.code {
|
||||
background: transparent url(../images/code.png) no-repeat 0 2px;
|
||||
width: 30px;
|
||||
height: 21px;
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
text-indent: -10000px;
|
||||
-moz-opacity: 0.8;
|
||||
filter:alpha(opacity=8);
|
||||
-webkit-transition: opacity ease-in-out 0.3s;
|
||||
}
|
||||
|
||||
a.zip:hover,
|
||||
a.tar:hover,
|
||||
a.code:hover {
|
||||
opacity: 1;
|
||||
-moz-opacity: 1;
|
||||
filter:alpha(opacity=1);
|
||||
}
|
||||
|
||||
a.download-button {
|
||||
border: 1px solid black;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
text-indent: 0!important;
|
||||
width: auto;
|
||||
float: right;
|
||||
background: #999; /* for non-css3 browsers */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#37ADD4', endColorstr='#1B657E'); /* for IE */
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#37ADD4), to(#1B657E)); /* for webkit browsers */
|
||||
background: -moz-linear-gradient(top, #37ADD4, #1B657E); /* for firefox 3.6+ */
|
||||
height: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
a.download-button span {
|
||||
background-position: 10px 5px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 5px 10px;
|
||||
padding-left: 45px;
|
||||
display: inline-block;
|
||||
text-indent: 0!important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-bottom: 60px;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
footer .owner {
|
||||
background: #222;
|
||||
border: 5px solid #444;
|
||||
padding: 5px 15px;
|
||||
margin: -67px -10px 35px;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
footer .creds small {
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
footer .owner .avatar {
|
||||
background-color: #666;
|
||||
display: block;
|
||||
margin: -19px 10px 0 0;
|
||||
width: 60px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
footer .owner img {
|
||||
display: block;
|
||||
border: 1px solid #2a2a2a;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
footer .owner p {
|
||||
font-family:Georgia, serif;
|
||||
}
|
||||
|
||||
footer .owner p a {
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Markdown */
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6,
|
||||
.markdown-body p,
|
||||
.markdown-body pre,
|
||||
.markdown-body ul,
|
||||
.markdown-body ol,
|
||||
.markdown-body dl,
|
||||
.markdown-body table,
|
||||
.markdown-body blockquote {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 24px;
|
||||
color: #557398;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
padding: 10px 70px 10px 0;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
font-family: 'Monaco', 'Lucida Console', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
box-shadow: inset 0 0 5px #000;
|
||||
word-wrap: break-word;
|
||||
background-color:#3b3b3b;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
.markdown-body pre.lines {
|
||||
font-size: 12px;
|
||||
margin:0 10px 0 -20px;
|
||||
padding: 10px;
|
||||
float: left;
|
||||
display: block;
|
||||
text-align: right;
|
||||
box-shadow: none;
|
||||
background-color:#2a2a2a;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-body li,
|
||||
.markdown-body li p,
|
||||
.markdown-body dd,
|
||||
.markdown-body dd p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.markdown-body li pre,
|
||||
.markdown-body li pre.lines,
|
||||
.markdown-body dd pre,
|
||||
.markdown-body dd pre.lines {
|
||||
margin-left: -35px;
|
||||
}
|
||||
|
||||
.markdown-body dt {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-body dd {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
width: 673px;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
||||
.markdown-body tbody {
|
||||
border-top: 2px solid #557398;
|
||||
border-bottom: 2px solid #557398;
|
||||
background-color: #EBEFF4;
|
||||
}
|
||||
|
||||
.markdown-body table td * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body td {
|
||||
border-right: 1px solid #557398;
|
||||
border-bottom: 1px solid #557398;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.markdown-body td:first-child,
|
||||
.markdown-body th:first-child {
|
||||
width: 30%;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-body td:last-child {
|
||||
border-right: 0 none;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.markdown-body tt {
|
||||
background-color:#3b3b3b;
|
||||
color: #d6d6d6;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
font-style: italic;
|
||||
font-family:Georgia, serif;
|
||||
font-size: 17px;
|
||||
border-top: 3px solid #333;
|
||||
border-bottom: 3px solid #333;
|
||||
padding: 10px 20px;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote:before {
|
||||
font-style: italic;
|
||||
font-family: Georgia, serif;
|
||||
font-size: 90px;
|
||||
height: 90px;
|
||||
margin-left: -60px;
|
||||
margin-top: -25px;
|
||||
content: "‟";
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
.highlight { background: #ffffff; }
|
||||
.highlight .c { color: #999988; font-style: italic } /* Comment */
|
||||
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
|
||||
.highlight .k { font-weight: bold } /* Keyword */
|
||||
.highlight .o { font-weight: bold } /* Operator */
|
||||
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
|
||||
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
|
||||
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
|
||||
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #aa0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #999999 } /* Generic.Heading */
|
||||
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
|
||||
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
|
||||
.highlight .go { color: #888888 } /* Generic.Output */
|
||||
.highlight .gp { color: #555555 } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
|
||||
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
|
||||
.highlight .kc { font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
|
||||
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .m { color: #009999 } /* Literal.Number */
|
||||
.highlight .s { color: #d14 } /* Literal.String */
|
||||
.highlight .na { color: #008080 } /* Name.Attribute */
|
||||
.highlight .nb { color: #0086B3 } /* Name.Builtin */
|
||||
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #008080 } /* Name.Constant */
|
||||
.highlight .ni { color: #800080 } /* Name.Entity */
|
||||
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
|
||||
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
|
||||
.highlight .nn { color: #555555 } /* Name.Namespace */
|
||||
.highlight .nt { color: #000080 } /* Name.Tag */
|
||||
.highlight .nv { color: #008080 } /* Name.Variable */
|
||||
.highlight .ow { font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mf { color: #009999 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
|
||||
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #d14 } /* Literal.String.Char */
|
||||
.highlight .sd { color: #d14 } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #d14 } /* Literal.String.Double */
|
||||
.highlight .se { color: #d14 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #d14 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #d14 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #009926 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #d14 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
|
||||
.highlight .vc { color: #008080 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #008080 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
|
||||
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
|
|
@ -1,130 +0,0 @@
|
|||
|
||||
require("mocha-as-promised")()
|
||||
chai = require "chai"
|
||||
chai.use require "chai-as-promised"
|
||||
{ expect } = chai
|
||||
|
||||
Q = require "q"
|
||||
merge = require "deepmerge"
|
||||
uuid = require "node-uuid"
|
||||
QStep = require "q-step"
|
||||
|
||||
activepush = require "../activepush"
|
||||
{ ActivePush } = activepush
|
||||
|
||||
TIMEOUT = 20000
|
||||
|
||||
createServer = (config) ->
|
||||
new ActivePush(config).start()
|
||||
|
||||
# These tests can be run either using socket.io-client within Node to simulate a browser, or using WebDriver to test real browsers
|
||||
exports.initIntegrationTests = (options) ->
|
||||
{ name, createClient } = options
|
||||
|
||||
config = activepush.config.loadConfiguration "test"
|
||||
# config.logging.level = "DEBUG"
|
||||
|
||||
describe "Single ActivePush instance (#{name})", ->
|
||||
@timeout TIMEOUT
|
||||
|
||||
server = null
|
||||
before ->
|
||||
# Use a unique inbox in case we're running multiple tests using the same ActiveMQ server concurrently etc
|
||||
inbox = uniqueInbox()
|
||||
createServer(merge(config, stomp:inbox:inbox)).then (activePush) ->
|
||||
server = activePush
|
||||
after ->
|
||||
server.stop()
|
||||
|
||||
it "should not buffer messages (treat as transient)", ->
|
||||
expected = uniqueMessage("NO")
|
||||
QStep(
|
||||
() -> server.producer.publish "my_push_id", expected
|
||||
() -> createClient server.config.http.port, "my_push_id"
|
||||
(getMessages) -> getMessages()
|
||||
(receivedMessages) -> expect(receivedMessages).to.deep.equal []
|
||||
)
|
||||
|
||||
it "should relay the correct messages to a single client", ->
|
||||
expected = uniqueMessage("YES")
|
||||
QStep(
|
||||
() -> createClient server.config.http.port, "my_push_id"
|
||||
(getMessages) ->
|
||||
@getMessages = getMessages
|
||||
Q.all [
|
||||
server.producer.publish "my_push_id", expected
|
||||
server.producer.publish "other_push_id", uniqueMessage("NO")
|
||||
]
|
||||
() -> @getMessages()
|
||||
(receivedMessages) -> expect(receivedMessages).to.deep.equal [expected]
|
||||
)
|
||||
|
||||
it "should relay the correct messages to multiple clients", (done) ->
|
||||
expected = uniqueMessage("YES")
|
||||
QStep(
|
||||
() -> Q.all(createClient(server.config.http.port, "my_push_id") for index in [0..1])
|
||||
(allGetMessages) ->
|
||||
@allGetMessages = allGetMessages
|
||||
Q.all [
|
||||
server.producer.publish "my_push_id", expected
|
||||
server.producer.publish "other_push_id", uniqueMessage("NO")
|
||||
]
|
||||
() -> Q.all(getMessages() for getMessages in @allGetMessages)
|
||||
(allReceivedMessages) -> expect(allReceivedMessages).to.deep.equal [[expected], [expected]]
|
||||
)
|
||||
|
||||
it "should relay multiple messages when using XHR transport", ->
|
||||
expected = uniqueMessage("YES")
|
||||
QStep(
|
||||
() -> createClient(server.config.http.port, "my_push_id", transports: ["xhr-polling"], 'try multiple transports': false)
|
||||
(getMessages) ->
|
||||
@getMessages = getMessages
|
||||
Q.all [
|
||||
server.producer.publish "my_push_id", expected
|
||||
server.producer.publish "my_push_id", expected
|
||||
]
|
||||
() -> @getMessages()
|
||||
(receivedMessages) -> expect(receivedMessages).to.deep.equal [expected, expected]
|
||||
)
|
||||
|
||||
describe "Multiple ActivePush instances (#{name})", ->
|
||||
@timeout TIMEOUT
|
||||
|
||||
servers = null
|
||||
before ->
|
||||
# Use a unique inbox in case we're running multiple tests using the same ActiveMQ server concurrently etc
|
||||
inbox = uniqueInbox()
|
||||
Q.all(for index in [0..1]
|
||||
createServer(merge(config,
|
||||
stomp:inbox: inbox
|
||||
http:port: config.http.port + index + 1 # Don't re-use the same port from first test
|
||||
))
|
||||
).then (_servers) ->
|
||||
servers = _servers
|
||||
after ->
|
||||
Q.all(server.stop() for server in servers)
|
||||
|
||||
it "should relay the correct messages to multiple clients", (done) ->
|
||||
QStep(
|
||||
() -> Q.all(createClient(server.config.http.port, "my_push_id") for server in servers)
|
||||
(allGetMessages) ->
|
||||
@allGetMessages = allGetMessages
|
||||
promises = []
|
||||
@expected = for server, index in servers
|
||||
promises.push server.producer.publish "my_push_id", (msg = uniqueMessage("YES#{index}"))
|
||||
promises.push server.producer.publish "other_push_id", uniqueMessage("NO")
|
||||
msg
|
||||
Q.all(promises)
|
||||
() -> Q.all(getMessages() for getMessages in @allGetMessages)
|
||||
(allReceivedMessages) ->
|
||||
allReceivedMessages = (messages.sort() for messages in allReceivedMessages)
|
||||
allExpectedMessages = (@expected.sort() for messages in allReceivedMessages)
|
||||
expect(allReceivedMessages).to.deep.equal allExpectedMessages
|
||||
)
|
||||
|
||||
uniqueInbox = ->
|
||||
"/topic/activepush-test-"+uuid.v1()
|
||||
|
||||
# Use unique messages to ensure we don't messages from other tests, etc.
|
||||
uniqueMessage = (prefix="") ->
|
||||
prefix+(if prefix then "-" else "")+uuid.v1()
|
|
@ -1,35 +0,0 @@
|
|||
|
||||
Q = require "q"
|
||||
io = require "socket.io-client"
|
||||
merge = require "deepmerge"
|
||||
|
||||
integration = require "./integration-common"
|
||||
|
||||
# HACK: Delay before checking received messages to ensure all messages get delivered.
|
||||
# Increase this value if tests are failiing non-deterministically.
|
||||
# TODO: better way to detect all messages have been delivered?
|
||||
WAIT_TIME = 200
|
||||
|
||||
exports.initIntegrationTests = ->
|
||||
|
||||
integration.initIntegrationTests
|
||||
name: "socket.io"
|
||||
createClient: (port, push_id, options = {}) ->
|
||||
deferred = Q.defer()
|
||||
socket = io.connect "http://localhost:#{port}", merge(options, "force new connection": true)
|
||||
# For some reason socket.io-client doesn't respect the "transports" option so we have to set it manually
|
||||
socket.socket.options.transports = options.transports if options.transports?
|
||||
socket.on "connect", ->
|
||||
socket.emit "subscribe", push_id
|
||||
messages = []
|
||||
socket.on "message", (message) ->
|
||||
messages.push message
|
||||
deferred.resolve ->
|
||||
Q.delay(WAIT_TIME).then -> messages
|
||||
# FIXME: figure out how to remove this delay (only required when using the XHR transport)
|
||||
if socket.socket.options.transports[0] is "xhr-polling" and socket.socket.options.transports.length is 1
|
||||
deferred.promise.delay(100)
|
||||
else
|
||||
deferred.promise
|
||||
|
||||
exports.initIntegrationTests()
|
|
@ -1,54 +0,0 @@
|
|||
|
||||
Q = require "q"
|
||||
QStep = require "q-step"
|
||||
wd = require "wd"
|
||||
merge = require "deepmerge"
|
||||
|
||||
integration = require "./integration-common"
|
||||
|
||||
# HACK: Delay before checking received messages to ensure all messages get delivered.
|
||||
# Increase this value if tests are failiing non-deterministically.
|
||||
# TODO: better way to detect all messages have been delivered?
|
||||
WAIT_TIME = 400
|
||||
|
||||
DEFAULT_CONFIG =
|
||||
browser:
|
||||
browserName: "firefox"
|
||||
|
||||
LOCAL_CONFIG =
|
||||
host: "localhost"
|
||||
port: 4444
|
||||
|
||||
SAUCE_CONFIG =
|
||||
host: "ondemand.saucelabs.com"
|
||||
port: 80
|
||||
username: process.env["SAUCE_USER"]
|
||||
password: process.env["SAUCE_KEY"]
|
||||
|
||||
if process.env["SAUCE_USER"]?
|
||||
CONFIG = merge(DEFAULT_CONFIG, SAUCE_CONFIG)
|
||||
else
|
||||
CONFIG = merge(DEFAULT_CONFIG, LOCAL_CONFIG)
|
||||
|
||||
exports.initIntegrationTests = (config = {}) ->
|
||||
config = merge(CONFIG, config)
|
||||
|
||||
integration.initIntegrationTests
|
||||
name: "webdriver-#{config.browser.browserName}"
|
||||
createClient: (port, push_id) ->
|
||||
browser = wd.promiseRemote(config.host, config.port, config.username, config.password)
|
||||
QStep(
|
||||
-> browser.init(config.browser)
|
||||
-> browser.get("http://localhost:#{port}/\##{push_id}")
|
||||
-> browser.waitForCondition("!!window.messages", 5000)
|
||||
->
|
||||
->
|
||||
Q.delay(WAIT_TIME).then ->
|
||||
browser.eval("window.messages").then (messages) ->
|
||||
messages
|
||||
.fin ->
|
||||
browser.quit()
|
||||
)
|
||||
|
||||
# exports.initIntegrationTests()
|
||||
exports.initIntegrationTests(browser: browserName: "chrome")
|
Loading…
Reference in New Issue