@depup/primus 8.0.9-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +36 -0
- package/changes.json +30 -0
- package/dist/primus.js +3663 -0
- package/errors.js +51 -0
- package/index.js +1153 -0
- package/middleware/access-control.js +20 -0
- package/middleware/authorization.js +57 -0
- package/middleware/error.js +41 -0
- package/middleware/forwarded.js +18 -0
- package/middleware/no-cache.js +25 -0
- package/middleware/primus.js +47 -0
- package/middleware/spec.js +36 -0
- package/middleware/xss.js +28 -0
- package/package.json +140 -0
- package/parsers/binary.js +54 -0
- package/parsers/ejson.js +41 -0
- package/parsers/json.js +35 -0
- package/parsers/msgpack.js +55 -0
- package/parsers.json +12 -0
- package/primus.js +1158 -0
- package/spark.js +515 -0
- package/transformer.js +237 -0
- package/transformers/browserchannel/client.js +92 -0
- package/transformers/browserchannel/index.js +19 -0
- package/transformers/browserchannel/server.js +64 -0
- package/transformers/engine.io/README.md +34 -0
- package/transformers/engine.io/client.js +111 -0
- package/transformers/engine.io/index.js +15 -0
- package/transformers/engine.io/library.js +2273 -0
- package/transformers/engine.io/server.js +63 -0
- package/transformers/faye/client.js +103 -0
- package/transformers/faye/index.js +12 -0
- package/transformers/faye/server.js +85 -0
- package/transformers/sockjs/README.md +31 -0
- package/transformers/sockjs/client.js +86 -0
- package/transformers/sockjs/index.js +15 -0
- package/transformers/sockjs/library.js +4201 -0
- package/transformers/sockjs/server.js +108 -0
- package/transformers/uws/index.js +12 -0
- package/transformers/uws/server.js +131 -0
- package/transformers/websockets/client.js +112 -0
- package/transformers/websockets/index.js +12 -0
- package/transformers/websockets/server.js +77 -0
- package/transformers.json +26 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sockjs = require('sockjs');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
|
|
6
|
+
const PrimusError = require('../../errors').PrimusError;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimum viable SockJS server for Node.js that works through the primus
|
|
10
|
+
* interface.
|
|
11
|
+
*
|
|
12
|
+
* @runat server
|
|
13
|
+
* @api private
|
|
14
|
+
*/
|
|
15
|
+
module.exports = function server() {
|
|
16
|
+
let fayeOptions = { maxLength: this.primus.options.maxLength };
|
|
17
|
+
let prefix = this.primus.pathname;
|
|
18
|
+
|
|
19
|
+
if (this.primus.options.compression) {
|
|
20
|
+
try {
|
|
21
|
+
fayeOptions.extensions = [ require('permessage-deflate') ];
|
|
22
|
+
} catch (e) {
|
|
23
|
+
[
|
|
24
|
+
'',
|
|
25
|
+
'Missing required npm dependency for sockjs',
|
|
26
|
+
'To use the permessage-deflate extension with the sockjs transformer, ',
|
|
27
|
+
'you have to install an additional dependency.',
|
|
28
|
+
'Please run the following command and try again:',
|
|
29
|
+
'',
|
|
30
|
+
' npm install --save permessage-deflate',
|
|
31
|
+
''
|
|
32
|
+
].forEach((line) => console.error(`Primus: ${line}`));
|
|
33
|
+
|
|
34
|
+
throw new PrimusError(
|
|
35
|
+
'Missing dependencies for transformer: "sockjs"',
|
|
36
|
+
this.primus
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (prefix.charAt(prefix.length - 1) !== '/') prefix += '(?:[^/]+)?';
|
|
42
|
+
|
|
43
|
+
this.service = sockjs.createServer();
|
|
44
|
+
|
|
45
|
+
//
|
|
46
|
+
// We've received a new connection, create a new Spark. The Spark will
|
|
47
|
+
// automatically announce it self as a new connection once it's created (after
|
|
48
|
+
// the next tick).
|
|
49
|
+
//
|
|
50
|
+
this.service.on('connection', (socket) => {
|
|
51
|
+
if (!socket) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const headers = socket.headers.via;
|
|
55
|
+
|
|
56
|
+
headers.via = headers._via;
|
|
57
|
+
socket.headers.via = null;
|
|
58
|
+
|
|
59
|
+
const spark = new this.Spark(
|
|
60
|
+
headers // HTTP request headers.
|
|
61
|
+
, socket // IP address location.
|
|
62
|
+
, url.parse(socket.url).query // Optional query string.
|
|
63
|
+
, socket.id // Unique connection id.
|
|
64
|
+
, null // We don't have have a HTTP request.
|
|
65
|
+
, socket // Reference to the SockJS socket.
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
spark.on('outgoing::end', () => socket && socket.close());
|
|
69
|
+
spark.on('outgoing::data', (data) => socket.write(data));
|
|
70
|
+
|
|
71
|
+
socket.on('error', spark.emits('incoming::error'));
|
|
72
|
+
socket.on('data', spark.emits('incoming::data'));
|
|
73
|
+
socket.on('close', spark.emits('incoming::end', (next) => {
|
|
74
|
+
socket.removeAllListeners();
|
|
75
|
+
socket = null;
|
|
76
|
+
next();
|
|
77
|
+
}));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
//
|
|
81
|
+
// Listen to requests.
|
|
82
|
+
//
|
|
83
|
+
const handle = this.service.listener(Object.assign({
|
|
84
|
+
faye_server_options: fayeOptions
|
|
85
|
+
}, this.primus.options.transport, {
|
|
86
|
+
log: this.logger.plain,
|
|
87
|
+
prefix: prefix
|
|
88
|
+
})).getHandler();
|
|
89
|
+
|
|
90
|
+
//
|
|
91
|
+
// Here be demons. SockJS has this really horrible "security" feature where it
|
|
92
|
+
// limits the HTTP headers that you're allowed to see and use in your
|
|
93
|
+
// applications. I whole heartly disagree with this decision so we're hacking
|
|
94
|
+
// around this by storing the full header in an accepted header key and re-use
|
|
95
|
+
// that when we construct a Primus Spark.
|
|
96
|
+
//
|
|
97
|
+
this.on('upgrade', (req, socket, head) => {
|
|
98
|
+
req.headers._via = req.headers.via;
|
|
99
|
+
req.headers.via = req.headers;
|
|
100
|
+
|
|
101
|
+
handle.call(this, req, socket, head);
|
|
102
|
+
}).on('request', (req, res) => {
|
|
103
|
+
req.headers._via = req.headers.via;
|
|
104
|
+
req.headers.via = req.headers;
|
|
105
|
+
|
|
106
|
+
handle.call(this, req, res);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
// Expose the module as new Transformer instance.
|
|
5
|
+
//
|
|
6
|
+
module.exports = require('../../transformer').extend({
|
|
7
|
+
// Creating a new real-time server.
|
|
8
|
+
server: require('./server'),
|
|
9
|
+
|
|
10
|
+
// The client-logic to connect with the server.
|
|
11
|
+
client: require('../websockets/client')
|
|
12
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
const uws = require('uws');
|
|
6
|
+
|
|
7
|
+
const native = uws.native;
|
|
8
|
+
|
|
9
|
+
//
|
|
10
|
+
// uws v0.12.0+ needs a set "no operation" callback.
|
|
11
|
+
//
|
|
12
|
+
if (native.setNoop) native.setNoop(() => {});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Server of µWebSockets transformer.
|
|
16
|
+
*
|
|
17
|
+
* @runat server
|
|
18
|
+
* @api private
|
|
19
|
+
*/
|
|
20
|
+
module.exports = function server() {
|
|
21
|
+
const opts = Object.assign({
|
|
22
|
+
perMessageDeflate: !!this.primus.options.compression,
|
|
23
|
+
maxPayload: this.primus.options.maxLength
|
|
24
|
+
}, this.primus.options.transport);
|
|
25
|
+
|
|
26
|
+
this.service = native.server;
|
|
27
|
+
|
|
28
|
+
let flags = 0;
|
|
29
|
+
|
|
30
|
+
if (opts.perMessageDeflate) {
|
|
31
|
+
flags |= uws.PERMESSAGE_DEFLATE;
|
|
32
|
+
if (opts.perMessageDeflate.serverNoContextTakeover === false) {
|
|
33
|
+
flags |= uws.SLIDING_DEFLATE_WINDOW;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const group = native.server.group.create(flags, opts.maxPayload);
|
|
38
|
+
let upgradeReq = null;
|
|
39
|
+
|
|
40
|
+
native.server.group.onConnection(group, (socket) => {
|
|
41
|
+
const spark = new this.Spark(
|
|
42
|
+
upgradeReq.headers, // HTTP request headers.
|
|
43
|
+
upgradeReq, // IP address location.
|
|
44
|
+
url.parse(upgradeReq.url).query, // Optional query string.
|
|
45
|
+
null, // We don't have an unique id.
|
|
46
|
+
upgradeReq, // Reference to the HTTP req.
|
|
47
|
+
socket // Reference to the socket.
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
native.setUserData(socket, spark);
|
|
51
|
+
|
|
52
|
+
spark.ultron.on('outgoing::end', () => native.server.close(socket));
|
|
53
|
+
spark.on('outgoing::data', (data) => {
|
|
54
|
+
const opcode = Buffer.isBuffer(data)
|
|
55
|
+
? uws.OPCODE_BINARY
|
|
56
|
+
: uws.OPCODE_TEXT;
|
|
57
|
+
|
|
58
|
+
native.server.send(socket, data, opcode, undefined, true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
native.server.group.onDisconnection(group, (socket, code, msg, spark) => {
|
|
63
|
+
native.clearUserData(socket);
|
|
64
|
+
spark.ultron.remove('outgoing::end');
|
|
65
|
+
spark.emit('incoming::end');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
native.server.group.onMessage(group, (msg, spark) => {
|
|
69
|
+
//
|
|
70
|
+
// Binary data is passed zero-copy as an `ArrayBuffer` so we first have to
|
|
71
|
+
// convert it to a `Buffer` and then copy it to a new `Buffer`.
|
|
72
|
+
//
|
|
73
|
+
if ('string' !== typeof msg) msg = Buffer.from(Buffer.from(msg));
|
|
74
|
+
|
|
75
|
+
spark.emit('incoming::data', msg);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
native.server.group.onPing(group, (msg, spark) => spark.emit('incoming::pong'));
|
|
79
|
+
|
|
80
|
+
//
|
|
81
|
+
// Listen to upgrade requests.
|
|
82
|
+
//
|
|
83
|
+
this.on('upgrade', (req, soc) => {
|
|
84
|
+
const secKey = req.headers['sec-websocket-key'];
|
|
85
|
+
|
|
86
|
+
if (soc.readable && soc.writable && secKey && secKey.length === 24) {
|
|
87
|
+
soc.setNoDelay(opts.noDelay);
|
|
88
|
+
|
|
89
|
+
let socketHandle = soc._handle;
|
|
90
|
+
let sslState = null;
|
|
91
|
+
|
|
92
|
+
if (soc.ssl) {
|
|
93
|
+
socketHandle = soc._parent._handle;
|
|
94
|
+
sslState = soc.ssl._external;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ticket = native.transfer(
|
|
98
|
+
socketHandle.fd === -1 ? socketHandle : socketHandle.fd,
|
|
99
|
+
sslState
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
soc.on('close', () => {
|
|
103
|
+
upgradeReq = req;
|
|
104
|
+
native.upgrade(
|
|
105
|
+
group,
|
|
106
|
+
ticket,
|
|
107
|
+
secKey,
|
|
108
|
+
req.headers['sec-websocket-extensions']
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
//
|
|
112
|
+
// Delete references to destroyed socket.
|
|
113
|
+
//
|
|
114
|
+
req.client = req.connection = req.socket = null;
|
|
115
|
+
upgradeReq = null;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
soc.destroy();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
//
|
|
123
|
+
// Listen to non-upgrade requests.
|
|
124
|
+
//
|
|
125
|
+
this.on('request', (req, res) => {
|
|
126
|
+
res.writeHead(426, { 'content-type': 'text/plain' });
|
|
127
|
+
res.end(http.STATUS_CODES[426]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.once('close', () => native.server.group.close(group));
|
|
131
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*globals MozWebSocket */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimum viable WebSocket client. This function is stringified and added
|
|
6
|
+
* in our client-side library.
|
|
7
|
+
*
|
|
8
|
+
* @runat client
|
|
9
|
+
* @api private
|
|
10
|
+
*/
|
|
11
|
+
module.exports = function client() {
|
|
12
|
+
var primus = this
|
|
13
|
+
, socket;
|
|
14
|
+
|
|
15
|
+
//
|
|
16
|
+
// Select an available WebSocket factory.
|
|
17
|
+
//
|
|
18
|
+
var Factory = (function factory() {
|
|
19
|
+
if ('undefined' !== typeof WebSocket) return WebSocket;
|
|
20
|
+
if ('undefined' !== typeof MozWebSocket) return MozWebSocket;
|
|
21
|
+
|
|
22
|
+
try { return Primus.requires('ws'); }
|
|
23
|
+
catch (e) {}
|
|
24
|
+
|
|
25
|
+
return undefined;
|
|
26
|
+
})();
|
|
27
|
+
|
|
28
|
+
if (!Factory) return primus.critical(new Error(
|
|
29
|
+
'Missing required `ws` module. Please run `npm install --save ws`'
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
//
|
|
33
|
+
// Connect to the given URL.
|
|
34
|
+
//
|
|
35
|
+
primus.on('outgoing::open', function opening() {
|
|
36
|
+
primus.emit('outgoing::end');
|
|
37
|
+
|
|
38
|
+
//
|
|
39
|
+
// FireFox will throw an error when we try to establish a connection from
|
|
40
|
+
// a secure page to an unsecured WebSocket connection. This is inconsistent
|
|
41
|
+
// behaviour between different browsers. This should ideally be solved in
|
|
42
|
+
// Primus when we connect.
|
|
43
|
+
//
|
|
44
|
+
try {
|
|
45
|
+
var options = {
|
|
46
|
+
protocol: primus.url.protocol === 'ws+unix:' ? 'ws+unix:' : 'ws:',
|
|
47
|
+
query: true
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//
|
|
51
|
+
// Only allow primus.transport object in Node.js, it will throw in
|
|
52
|
+
// browsers with a TypeError if we supply to much arguments.
|
|
53
|
+
//
|
|
54
|
+
if (Factory.length === 3) {
|
|
55
|
+
if ('ws+unix:' === options.protocol) {
|
|
56
|
+
options.pathname =
|
|
57
|
+
'/' +
|
|
58
|
+
primus.url.hostname +
|
|
59
|
+
primus.url.pathname +
|
|
60
|
+
':' +
|
|
61
|
+
primus.pathname;
|
|
62
|
+
}
|
|
63
|
+
primus.socket = socket = new Factory(
|
|
64
|
+
primus.uri(options), // URL
|
|
65
|
+
[], // Sub protocols
|
|
66
|
+
primus.transport // options.
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
primus.socket = socket = new Factory(primus.uri(options));
|
|
70
|
+
socket.binaryType = 'arraybuffer';
|
|
71
|
+
}
|
|
72
|
+
} catch (e) { return primus.emit('error', e); }
|
|
73
|
+
|
|
74
|
+
//
|
|
75
|
+
// Setup the Event handlers.
|
|
76
|
+
//
|
|
77
|
+
socket.onopen = primus.emits('incoming::open');
|
|
78
|
+
socket.onerror = primus.emits('incoming::error');
|
|
79
|
+
socket.onclose = primus.emits('incoming::end');
|
|
80
|
+
socket.onmessage = primus.emits('incoming::data', function parse(next, evt) {
|
|
81
|
+
next(undefined, evt.data);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
//
|
|
86
|
+
// We need to write a new message to the socket.
|
|
87
|
+
//
|
|
88
|
+
primus.on('outgoing::data', function write(message) {
|
|
89
|
+
if (!socket || socket.readyState !== Factory.OPEN) return;
|
|
90
|
+
|
|
91
|
+
try { socket.send(message); }
|
|
92
|
+
catch (e) { primus.emit('incoming::error', e); }
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
//
|
|
96
|
+
// Attempt to reconnect the socket.
|
|
97
|
+
//
|
|
98
|
+
primus.on('outgoing::reconnect', function reconnect() {
|
|
99
|
+
primus.emit('outgoing::open');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
//
|
|
103
|
+
// We need to close the socket.
|
|
104
|
+
//
|
|
105
|
+
primus.on('outgoing::end', function close() {
|
|
106
|
+
if (!socket) return;
|
|
107
|
+
|
|
108
|
+
socket.onerror = socket.onopen = socket.onclose = socket.onmessage = function () {};
|
|
109
|
+
socket.close();
|
|
110
|
+
socket = null;
|
|
111
|
+
});
|
|
112
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
// Expose the module as new Transformer instance.
|
|
5
|
+
//
|
|
6
|
+
module.exports = require('../../transformer').extend({
|
|
7
|
+
// Creating a new real-time server.
|
|
8
|
+
server: require('./server'),
|
|
9
|
+
|
|
10
|
+
// The client-logic to connect with the server.
|
|
11
|
+
client: require('./client')
|
|
12
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
const ws = require('ws');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Minimum viable WebSocket server for Node.js that works through the Primus
|
|
9
|
+
* interface.
|
|
10
|
+
*
|
|
11
|
+
* @runat server
|
|
12
|
+
* @api private
|
|
13
|
+
*/
|
|
14
|
+
module.exports = function server() {
|
|
15
|
+
this.service = new ws.Server(Object.assign({
|
|
16
|
+
perMessageDeflate: !!this.primus.options.compression,
|
|
17
|
+
maxPayload: this.primus.options.maxLength
|
|
18
|
+
}, this.primus.options.transport, {
|
|
19
|
+
clientTracking: false,
|
|
20
|
+
noServer: true
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Noop! Pointless, empty function that will actually be really useful.
|
|
25
|
+
*
|
|
26
|
+
* @param {Error} err We failed at something.
|
|
27
|
+
* @api private
|
|
28
|
+
*/
|
|
29
|
+
const noop = (err) => err && this.logger.error(err);
|
|
30
|
+
|
|
31
|
+
//
|
|
32
|
+
// Listen to upgrade requests.
|
|
33
|
+
//
|
|
34
|
+
this.on('upgrade', (req, socket, head) => {
|
|
35
|
+
this.service.handleUpgrade(req, socket, head, (socket) => {
|
|
36
|
+
const spark = new this.Spark(
|
|
37
|
+
req.headers // HTTP request headers.
|
|
38
|
+
, req // IP address location.
|
|
39
|
+
, url.parse(req.url).query // Optional query string.
|
|
40
|
+
, null // We don't have an unique id.
|
|
41
|
+
, req // Reference to the HTTP req.
|
|
42
|
+
, socket // Reference to the WebSocket.
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
spark.on('outgoing::end', () => socket && socket.close());
|
|
46
|
+
spark.on('outgoing::data', (data) => {
|
|
47
|
+
if (socket.readyState !== socket.OPEN) return;
|
|
48
|
+
socket.send(data, noop);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
socket.on('message',
|
|
52
|
+
spark.emits('incoming::data', (next, data, isBinary) => {
|
|
53
|
+
next(undefined, isBinary === false ? data.toString() : data);
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
socket.on('error', spark.emits('incoming::error'));
|
|
57
|
+
socket.on('ping', spark.emits('incoming::pong', (next) => {
|
|
58
|
+
next(undefined, null);
|
|
59
|
+
}));
|
|
60
|
+
socket.on('close', spark.emits('incoming::end', (next) => {
|
|
61
|
+
socket.removeAllListeners();
|
|
62
|
+
socket = null;
|
|
63
|
+
next();
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
//
|
|
69
|
+
// Listen to non-upgrade requests.
|
|
70
|
+
//
|
|
71
|
+
this.on('request', (req, res) => {
|
|
72
|
+
res.writeHead(426, { 'content-type': 'text/plain' });
|
|
73
|
+
res.end(http.STATUS_CODES[426]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.once('close', () => this.service.close());
|
|
77
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"websockets": {
|
|
3
|
+
"server": "ws",
|
|
4
|
+
"client": "ws"
|
|
5
|
+
},
|
|
6
|
+
"engine.io": {
|
|
7
|
+
"server": "engine.io",
|
|
8
|
+
"client": "engine.io-client"
|
|
9
|
+
},
|
|
10
|
+
"browserchannel": {
|
|
11
|
+
"server": "browserchannel",
|
|
12
|
+
"client": "browserchannel"
|
|
13
|
+
},
|
|
14
|
+
"sockjs": {
|
|
15
|
+
"server": "sockjs",
|
|
16
|
+
"client": "sockjs-client"
|
|
17
|
+
},
|
|
18
|
+
"faye": {
|
|
19
|
+
"server": "faye-websocket",
|
|
20
|
+
"client": "faye-websocket"
|
|
21
|
+
},
|
|
22
|
+
"uws": {
|
|
23
|
+
"server": "uws",
|
|
24
|
+
"client": "ws"
|
|
25
|
+
}
|
|
26
|
+
}
|