[Pkg-javascript-commits] [sockjs-client] 138/350: More tests
tonnerre at ancient-solutions.com
tonnerre at ancient-solutions.com
Fri Aug 5 01:03:55 UTC 2016
This is an automated email from the git hooks/post-receive script.
tonnerre-guest pushed a commit to branch upstream
in repository sockjs-client.
commit 71bd36260ed87ecca07100d2b7a9bd0ca1c8260a
Author: Bryce Kahle <bkahle at gmail.com>
Date: Tue Oct 14 19:31:24 2014 -0400
More tests
---
.zuul.yml | 1 -
lib/main.js | 23 +++++----
lib/transport/driver/xhr.js | 9 +++-
lib/transport/lib/iframe-wrap.js | 4 ++
lib/transport/receiver/xhr.js | 12 +----
lib/transport/websocket.js | 7 +--
lib/transport/xhr-streaming.js | 3 +-
lib/utils/transport.js | 5 ++
tests/browser.js | 10 ++--
tests/lib/end-to-end.js | 83 +++++++++++++++++++++++++++++
tests/lib/iframe.js | 109 +++++++++++++++++++++++++++++++++++++++
tests/lib/senders.js | 81 +++++++++++++++++++++++++++++
tests/lib/test-utils.js | 51 ++++++++++++++++++
tests/lib/transports.js | 58 +++++++++++++++++++--
tests/node.js | 13 +++--
tests/support/sockjs_app.js | 1 +
16 files changed, 434 insertions(+), 36 deletions(-)
diff --git a/.zuul.yml b/.zuul.yml
index e7a84f5..4cc9394 100644
--- a/.zuul.yml
+++ b/.zuul.yml
@@ -1,6 +1,5 @@
ui: mocha-bdd
scripts:
- - "config.js"
- "domain.js"
server: ./tests/support/sockjs_server.js
browsers:
diff --git a/lib/main.js b/lib/main.js
index 7ae30a8..7d4d158 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -131,8 +131,10 @@ SockJS.prototype.close = function(code, reason) {
};
SockJS.prototype.send = function(data) {
+ // TODO seems we should object WS spec and not allow anything but strings
if (typeof data !== 'string') {
- throw new TypeError('data must be a string');
+ data = '' + data;
+ //throw new TypeError('data must be a string');
}
if (this.readyState === SockJS.CONNECTING) {
throw new InvalidStateError('The connection has not been established yet');
@@ -194,6 +196,7 @@ SockJS.prototype._connect = function() {
var transport = new Transport(transportUrl, this._transUrl);
transport.on('message', this._transportMessage.bind(this));
transport.once('close', this._transportClose.bind(this));
+ transport.transportName = Transport.transportName;
this._transport = transport;
return;
@@ -220,31 +223,32 @@ SockJS.prototype._transportMessage = function(msg) {
case 'a':
payload = JSON3.parse(msg.slice(1) || '[]');
payload.forEach(function (p) {
- debug('message', p);
+ debug('message', self.transport, p);
self.dispatchEvent(new TransportMessageEvent(p));
});
break;
case 'm':
payload = JSON3.parse(msg.slice(1) || 'null');
- debug('message', payload);
+ debug('message', this.transport, payload);
this.dispatchEvent(new TransportMessageEvent(payload));
break;
case 'c':
payload = JSON3.parse(msg.slice(1) || '[]');
- this._close(payload[0], payload[1]);
+ this._close(payload[0], payload[1], true);
break;
case 'h':
this.dispatchEvent(new Event('heartbeat'));
- debug('heartbeat');
+ debug('heartbeat', this.transport);
break;
}
};
SockJS.prototype._transportClose = function(code, reason) {
- debug('_transportClose', code, reason);
+ debug('_transportClose', this.transport, code, reason);
if (this._transport) {
this._transport.removeAllListeners();
this._transport = null;
+ this.transport = null;
}
if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {
@@ -256,7 +260,7 @@ SockJS.prototype._transportClose = function(code, reason) {
};
SockJS.prototype._open = function() {
- debug('_open', this.readyState);
+ debug('_open', this._transport.transportName, this.readyState);
if (this.readyState === SockJS.CONNECTING) {
if (this._transportTimeoutId) {
clearTimeout(this._transportTimeoutId);
@@ -274,7 +278,7 @@ SockJS.prototype._open = function() {
};
SockJS.prototype._close = function(code, reason, wasClean) {
- debug('_close', code, reason, wasClean, this.readyState);
+ debug('_close', this.transport, code, reason, wasClean, this.readyState);
var forceFail = false;
if (this._ir) {
@@ -286,6 +290,7 @@ SockJS.prototype._close = function(code, reason, wasClean) {
this._transport.removeAllListeners();
this._transport.close();
this._transport = null;
+ this.transport = null;
}
if (this.readyState === SockJS.CLOSED) {
@@ -301,7 +306,7 @@ SockJS.prototype._close = function(code, reason, wasClean) {
}
var e = new CloseEvent('close');
- e.wasClean = wasClean;
+ e.wasClean = wasClean || false;
e.code = code;
e.reason = reason;
diff --git a/lib/transport/driver/xhr.js b/lib/transport/driver/xhr.js
index e284376..a407a1e 100644
--- a/lib/transport/driver/xhr.js
+++ b/lib/transport/driver/xhr.js
@@ -4,9 +4,11 @@ var EventEmitter = require('events').EventEmitter
, util = require('util')
, http = require('http')
, u = require('url')
+ , debug = require('debug')('sockjs-client:driver:xhr')
;
function XhrDriver(method, url, payload, opts) {
+ debug(method, url, payload);
var self = this;
EventEmitter.call(this);
@@ -22,18 +24,22 @@ function XhrDriver(method, url, payload, opts) {
this.req = http.request(options, function (res) {
res.setEncoding('utf8');
- var responseText;
+ var responseText = '';
res.on('data', function (chunk) {
+ debug('data', chunk);
responseText += chunk;
+ self.emit('chunk', 200, responseText);
});
res.once('end', function () {
+ debug('end');
self.emit('finish', res.statusCode, responseText);
self.req = null;
});
});
this.req.on('error', function (e) {
+ debug('error', e);
self.emit('finish', 0, e.message);
});
@@ -46,6 +52,7 @@ function XhrDriver(method, url, payload, opts) {
util.inherits(XhrDriver, EventEmitter);
XhrDriver.prototype.close = function() {
+ debug('close');
this.removeAllListeners();
if (this.req) {
this.req.abort();
diff --git a/lib/transport/lib/iframe-wrap.js b/lib/transport/lib/iframe-wrap.js
index 87e9856..a0b487b 100644
--- a/lib/transport/lib/iframe-wrap.js
+++ b/lib/transport/lib/iframe-wrap.js
@@ -14,6 +14,10 @@ module.exports = function (transport) {
util.inherits(IframeWrapTransport, IframeTransport);
IframeWrapTransport.enabled = function (url, info) {
+ if (!global.document) {
+ return false;
+ }
+
var iframeInfo = objectUtils.extend({}, info);
iframeInfo.sameOrigin = true;
return transport.enabled(url, iframeInfo) && IframeTransport.enabled();
diff --git a/lib/transport/receiver/xhr.js b/lib/transport/receiver/xhr.js
index 6e2b5b3..dce50a3 100644
--- a/lib/transport/receiver/xhr.js
+++ b/lib/transport/receiver/xhr.js
@@ -6,7 +6,7 @@ var util = require('util')
;
function XhrReceiver(url, AjaxObject) {
- debug('url');
+ debug(url);
EventEmitter.call(this);
var self = this;
@@ -45,16 +45,6 @@ XhrReceiver.prototype._chunkHandler = function (status, text) {
this.emit('message', msg);
}
}
-
- // var self = this;
- // var messages = text.split('\n');
- // messages.forEach(function (msg) {
- // if (!msg) {
- // return;
- // }
- // debug('message', msg);
- // self.emit('message', msg);
- // });
};
XhrReceiver.prototype._cleanup = function () {
diff --git a/lib/transport/websocket.js b/lib/transport/websocket.js
index 60bbcd0..c4d2ab9 100644
--- a/lib/transport/websocket.js
+++ b/lib/transport/websocket.js
@@ -36,7 +36,7 @@ function WebSocketTransport(transUrl) {
self.ws.close();
});
this.ws.onclose = function(e) {
- debug('close event', e);
+ debug('close event', e.code, e.reason);
self.emit('close', e.code, e.reason);
self._cleanup();
};
@@ -50,8 +50,9 @@ function WebSocketTransport(transUrl) {
util.inherits(WebSocketTransport, EventEmitter);
WebSocketTransport.prototype.send = function(data) {
- debug('send', data);
- this.ws.send(data);
+ var msg = '[' + data + ']';
+ debug('send', msg);
+ this.ws.send(msg);
};
WebSocketTransport.prototype.close = function() {
diff --git a/lib/transport/xhr-streaming.js b/lib/transport/xhr-streaming.js
index af572e7..6dea149 100644
--- a/lib/transport/xhr-streaming.js
+++ b/lib/transport/xhr-streaming.js
@@ -35,6 +35,7 @@ XhrStreamingTransport.roundTrips = 2; // preflight, ajax
// Safari gets confused when a streaming ajax request is started
// before onload. This causes the load indicator to spin indefinetely.
-XhrStreamingTransport.needBody = true;
+// Only require body when used in a browser
+XhrStreamingTransport.needBody = !!global.document;
module.exports = XhrStreamingTransport;
diff --git a/lib/utils/transport.js b/lib/utils/transport.js
index a9d0a06..756249f 100644
--- a/lib/utils/transport.js
+++ b/lib/utils/transport.js
@@ -20,6 +20,11 @@ module.exports = function (availableTransports) {
return;
}
+ if (trans.transportName === 'websocket' && info.websocket === false) {
+ debug('disabled from server', 'websocket');
+ return;
+ }
+
if (transportsWhitelist.length &&
transportsWhitelist.indexOf(trans.transportName) === -1) {
debug('not in whitelist', trans.transportName);
diff --git a/tests/browser.js b/tests/browser.js
index d17cc71..b18645c 100644
--- a/tests/browser.js
+++ b/tests/browser.js
@@ -5,6 +5,10 @@ require('../lib/shims');
// prevent global leak warnings on this
global._jp = {};
-require('./lib/main.js');
-require('./lib/utils.js');
-require('./lib/receivers.js');
+require('./lib/main');
+require('./lib/utils');
+require('./lib/receivers');
+require('./lib/senders');
+require('./lib/end-to-end');
+require('./lib/iframe');
+require('./lib/transports');
diff --git a/tests/lib/end-to-end.js b/tests/lib/end-to-end.js
new file mode 100644
index 0000000..aef88eb
--- /dev/null
+++ b/tests/lib/end-to-end.js
@@ -0,0 +1,83 @@
+'use strict';
+
+var expect = require('expect.js')
+ , testUtils = require('./test-utils')
+ ;
+
+describe('End to End', function () {
+ this.timeout(10000);
+
+ describe('Connection Errors', function () {
+ it('invalid url 404', function (done) {
+ var sjs = testUtils.newSockJs('/invalid_url', 'jsonp-polling');
+ expect(sjs).to.be.ok();
+ sjs.onopen = sjs.onmessage = function () {
+ expect().fail('Open/Message event should not fire for an invalid url');
+ };
+ sjs.onclose = function (e) {
+ expect(e.code).to.equal(1002);
+ expect(e.reason).to.equal('Cannot connect to server');
+ expect(e.wasClean).to.equal(false);
+ done();
+ };
+ });
+
+ it('invalid url port', function (done) {
+ var badUrl;
+ if (global.location) {
+ badUrl = global.location.protocol + '//' + global.location.hostname + ':1079';
+ } else {
+ badUrl = 'http://localhost:1079';
+ }
+
+ var sjs = testUtils.newSockJs(badUrl, 'jsonp-polling');
+ expect(sjs).to.be.ok();
+ sjs.onopen = sjs.onmessage = function () {
+ expect().fail('Open/Message event should not fire for an invalid port');
+ };
+ sjs.onclose = function (e) {
+ expect(e.code).to.equal(1002);
+ expect(e.reason).to.equal('Cannot connect to server');
+ expect(e.wasClean).to.equal(false);
+ done();
+ };
+ });
+
+ it('disabled websocket test', function (done) {
+ var sjs = testUtils.newSockJs('/disabled_websocket_echo', 'websocket');
+ expect(sjs).to.be.ok();
+ sjs.onopen = sjs.onmessage = function () {
+ expect().fail('Open/Message event should not fire for disabled websockets');
+ };
+ sjs.onclose = function (e) {
+ expect(e.code).to.equal(2000);
+ expect(e.reason).to.equal('All transports failed');
+ expect(e.wasClean).to.equal(false);
+ done();
+ };
+ });
+
+ it('close on close', function (done) {
+ var sjs = testUtils.newSockJs('/close');
+ expect(sjs).to.be.ok();
+ sjs.onopen = function () {
+ expect(true).to.be.ok();
+ };
+ sjs.onmessage = function () {
+ expect().fail('Message should not be emitted');
+ };
+ sjs.onclose = function (e) {
+ expect(e.code).to.equal(3000);
+ expect(e.reason).to.equal('Go away!');
+ expect(e.wasClean).to.equal(true);
+ sjs.onclose = function () {
+ expect().fail();
+ };
+ sjs.close();
+ setTimeout(function () {
+ done();
+ }, 10);
+ };
+ });
+ });
+});
diff --git a/tests/lib/iframe.js b/tests/lib/iframe.js
new file mode 100644
index 0000000..30910c5
--- /dev/null
+++ b/tests/lib/iframe.js
@@ -0,0 +1,109 @@
+/* eslint quotes: 0 */
+'use strict';
+
+var expect = require('expect.js')
+ , eventUtils = require('../../lib/utils/event')
+ , testUtils = require('./test-utils')
+ , IframeTransport = require('../../lib/transport/iframe')
+ ;
+
+var originUrl;
+if (global.location) {
+ originUrl = global.location.origin;
+} else {
+ originUrl = 'http://localhost:8081';
+}
+
+function onunloadTest (code, done) {
+ var hook = testUtils.createIframe();
+ var i = 0;
+ hook.open = function () {
+ i++;
+ return hook.callback(code);
+ };
+ hook.load = function () {
+ i++;
+ return setTimeout(function () { hook.iobj.cleanup(); }, 1);
+ };
+ hook.unload = function () {
+ expect(i).to.equal(2);
+ hook.del();
+ done();
+ };
+}
+
+describe('iframe', function () {
+ if (!IframeTransport.enabled()) {
+ it('[unsupported]', function () { expect(true).to.be.ok(); });
+ return;
+ }
+
+ it('onunload', function (done) {
+ this.runnable().globals(['_sockjs_global']);
+ this.timeout(5000);
+ onunloadTest("function attachEvent(event, listener) {" +
+ " if (typeof window.addEventListener !== 'undefined') {" +
+ " window.addEventListener(event, listener, false);" +
+ " } else {" +
+ " document.attachEvent('on' + event, listener);" +
+ " window.attachEvent('on' + event, listener);" +
+ " }" +
+ "}" +
+ "attachEvent('load', function(){" +
+ " hook.load();" +
+ "});" +
+ "var w = 0;" +
+ "var run = function(){" +
+ " if(w === 0) {" +
+ " w = 1;" +
+ " hook.unload();" +
+ " }" +
+ "};" +
+ "attachEvent('beforeunload', run);" +
+ "attachEvent('unload', run);", done);
+ });
+
+ it('onmessage', function (done) {
+ this.runnable().globals(['_sockjs_global']);
+ var hook = testUtils.createIframe();
+ var i = 0;
+ hook.open = function () {
+ i++;
+ hook.callback("" +
+ "function attachEvent(event, listener) {" +
+ " if (typeof window.addEventListener !== 'undefined') {" +
+ " window.addEventListener(event, listener, false);" +
+ " } else {" +
+ " document.attachEvent('on' + event, listener);" +
+ " window.attachEvent('on' + event, listener);" +
+ " }" +
+ "}" +
+ "attachEvent('message', function(e) {" +
+ " var b = e.data;" +
+ " parent.postMessage(window_id + ' ' + 'e', '*');" +
+ "});" +
+ "parent.postMessage(window_id + ' ' + 's', '*');");
+ };
+ eventUtils.attachEvent('message', function (e) {
+ var msgParts = e.data.split(' ')
+ , windowId = msgParts[0]
+ , data = msgParts[1]
+ ;
+ if (windowId === hook.id) {
+ switch (data) {
+ case 's':
+ hook.iobj.loaded();
+ i++;
+ hook.iobj.post(hook.id + ' ' + 's', originUrl);
+ break;
+ case 'e':
+ expect(i).to.equal(2);
+ hook.iobj.cleanup();
+ hook.del();
+ done();
+ break;
+ }
+ }
+ });
+ });
+});
diff --git a/tests/lib/senders.js b/tests/lib/senders.js
new file mode 100644
index 0000000..3443868
--- /dev/null
+++ b/tests/lib/senders.js
@@ -0,0 +1,81 @@
+'use strict';
+
+var expect = require('expect.js')
+ , XhrLocal = require('../../lib/transport/sender/xhr-local')
+ , Xdr = require('../../lib/transport/sender/xdr')
+ ;
+
+var originUrl;
+if (global.location) {
+ originUrl = global.location.origin;
+} else {
+ originUrl = 'http://localhost:8081';
+}
+
+function ajaxSimple (Obj) {
+ it('simple', function (done) {
+ var x = new Obj('GET', originUrl + '/simple.txt', null);
+ x.on('finish', function (status, text) {
+ expect(text.length).to.equal(2051);
+ expect(text.slice(-2)).to.equal('b\n');
+ done();
+ });
+ });
+}
+
+function ajaxStreaming (Obj) {
+ it('streaming', function (done) {
+ var x = new Obj('GET', originUrl + '/streaming.txt', null);
+ x.on('chunk', function (status, text) {
+ expect(status).to.equal(200);
+ expect(text.length).to.be.lessThan(2050);
+ x.removeAllListeners('chunk');
+ });
+ x.on('finish', function (status, text) {
+ expect(status).to.equal(200);
+ expect(text.slice(-4)).to.equal('a\nb\n');
+ done();
+ });
+ });
+}
+
+function wrongUrl(Obj, url, statuses) {
+ it('wrong url ' + url, function (done) {
+ var x = new Obj('GET', url, null);
+ x.on('chunk', function () {
+ expect().fail('No chunk should be received');
+ });
+ x.on('finish', function (status, text) {
+ expect(statuses).to.contain(status);
+ expect(text).not.to.be(undefined);
+ done();
+ });
+ });
+}
+
+function wrongPort (Obj) {
+ var ports = [25, 8999, 65300];
+ ports.forEach(function (port) {
+ wrongUrl(Obj, 'http://localhost:' + port + '/wrong_url_indeed.txt', [0]);
+ });
+}
+
+describe('Senders', function () {
+ describe('xhr-local', function () {
+ ajaxSimple(XhrLocal);
+ ajaxStreaming(XhrLocal);
+ wrongPort(XhrLocal);
+ wrongUrl(XhrLocal, originUrl + '/wrong_url_indeed.txt', [0, 404]);
+ });
+
+ describe('xdr', function () {
+ if (!Xdr.enabled) {
+ it('[unsupported]', function() { expect(true).to.be.ok(); });
+ return;
+ }
+ ajaxSimple(Xdr);
+ ajaxStreaming(Xdr);
+ wrongPort(Xdr);
+ wrongUrl(Xdr, originUrl + '/wrong_url_indeed.txt', [0]);
+ });
+});
diff --git a/tests/lib/test-utils.js b/tests/lib/test-utils.js
new file mode 100644
index 0000000..eb1f771
--- /dev/null
+++ b/tests/lib/test-utils.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var SockJS = require('../../lib/entry')
+ , iframeUtils = require('../../lib/utils/iframe')
+ , random = require('../../lib/utils/random')
+ ;
+
+var originUrl;
+if (global.location) {
+ originUrl = global.location.origin;
+} else {
+ originUrl = 'http://localhost:8081';
+}
+
+var MPrefix = '_sockjs_global';
+
+module.exports = {
+ getUrl: function (path) {
+ return /^http/.test(path) ? path : originUrl + path;
+ }
+
+, newSockJs: function (path, transport) {
+ return new SockJS(this.getUrl(path), null, transport);
+ }
+
+, createHook: function () {
+ var windowId = 'a' + random.string(8);
+ if (!(MPrefix in global)) {
+ var map = {};
+ global[MPrefix] = function(windowId) {
+ if (!(windowId in map)) {
+ map[windowId] = {
+ id: windowId,
+ del: function() {delete map[windowId];}
+ };
+ }
+ return map[windowId];
+ };
+ }
+ return global[MPrefix](windowId);
+ }
+
+, createIframe: function (path) {
+ path = path || '/iframe.html';
+ var hook = this.createHook();
+ hook.iobj = iframeUtils.createIframe(path + '?a=' + random.number(1000) + '#' + hook.id, function () {
+ throw new Error('iframe error');
+ });
+ return hook;
+ }
+};
diff --git a/tests/lib/transports.js b/tests/lib/transports.js
index 83d3fa7..2c7b971 100644
--- a/tests/lib/transports.js
+++ b/tests/lib/transports.js
@@ -1,9 +1,54 @@
+/* eslint camelcase: 0 */
'use strict';
var expect = require('expect.js')
, transportList = require('../../lib/transport-list')
+ , testUtils = require('./test-utils')
;
+function echoFactory(transport, messages) {
+ return function (done) {
+ this.timeout(10000);
+ this.runnable().globals(['_sockjs_global']);
+ var msgs = messages.slice(0);
+
+ var sjs = testUtils.newSockJs('/echo', transport);
+ sjs.onopen = function () {
+ sjs.send(msgs[0]);
+ };
+ sjs.onmessage = function (e) {
+ // TODO don't like having to force the element toString here
+ expect(e.data).to.eql('' + msgs[0]);
+ msgs.shift();
+ if (typeof msgs[0] === 'undefined') {
+ sjs.close();
+ } else {
+ sjs.send(msgs[0]);
+ }
+ };
+ sjs.onclose = function (e) {
+ expect(e.code).to.equal(1000);
+ expect(msgs).to.have.length(0);
+ done();
+ };
+ };
+}
+
+function echoBasic(transport) {
+ var messages = ['data'];
+ it('echo basic', echoFactory(transport, messages));
+}
+
+function echoRich(transport) {
+ var messages = [
+ [1, 2, 3, 'data'], null, false, 'data', 1, 12.0, {
+ a: 1,
+ b: 2
+ }
+ ];
+ it('echo rich', echoFactory(transport, messages));
+}
+
describe('Transports', function () {
transportList.forEach(function (Trans) {
describe(Trans.transportName, function () {
@@ -18,17 +63,24 @@ describe('Transports', function () {
expect(Trans).to.have.property('enabled');
expect(Trans.enabled).to.be.a('function');
- //var t = new Trans('http://localhost');
-
expect(Trans.prototype).to.have.property('send');
expect(Trans.prototype.send).to.be.a('function');
expect(Trans.prototype).to.have.property('close');
expect(Trans.prototype.close).to.be.a('function');
- //expect().to.be.an(EventEmitter);
+ //var t = new Trans('http://localhost');
+ //expect(t).to.be.an(EventEmitter);
// TODO tests for event emitting
});
+
+ if (!Trans.enabled(testUtils.getUrl('/echo'), { cookie_needed: false, nullOrigin: false })) {
+ it('[unsupported]', function () { expect(true).to.be.ok(); });
+ return;
+ }
+
+ echoBasic(Trans.transportName);
+ echoRich(Trans.transportName);
});
});
});
diff --git a/tests/node.js b/tests/node.js
index 3ba7bc3..4c2d787 100644
--- a/tests/node.js
+++ b/tests/node.js
@@ -1,6 +1,11 @@
'use strict';
-require('./lib/main.js');
-require('./lib/main-node.js');
-require('./lib/utils.js');
-require('./lib/receivers.js');
+require('./support/sockjs_server');
+
+require('./lib/main');
+require('./lib/main-node');
+require('./lib/utils');
+require('./lib/receivers');
+require('./lib/senders');
+require('./lib/end-to-end');
+require('./lib/transports');
diff --git a/tests/support/sockjs_app.js b/tests/support/sockjs_app.js
index b6dcf34..a4c6bec 100644
--- a/tests/support/sockjs_app.js
+++ b/tests/support/sockjs_app.js
@@ -10,6 +10,7 @@ exports.install = function(opts, server) {
console.log(' [-] echo close ' + conn);
});
conn.on('data', function(m) {
+ console.log(m);
var d = JSON.stringify(m);
console.log(' [ ] echo message ' + conn,
d.slice(0,64) +
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/sockjs-client.git
More information about the Pkg-javascript-commits
mailing list