@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
package/primus.js
ADDED
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
/*globals require, define */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var EventEmitter = require('eventemitter3')
|
|
5
|
+
, TickTock = require('tick-tock')
|
|
6
|
+
, Recovery = require('recovery')
|
|
7
|
+
, qs = require('querystringify')
|
|
8
|
+
, inherits = require('inherits')
|
|
9
|
+
, destroy = require('demolish')
|
|
10
|
+
, yeast = require('yeast')
|
|
11
|
+
, u2028 = /\u2028/g
|
|
12
|
+
, u2029 = /\u2029/g;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Context assertion, ensure that some of our public Primus methods are called
|
|
16
|
+
* with the correct context to ensure that
|
|
17
|
+
*
|
|
18
|
+
* @param {Primus} self The context of the function.
|
|
19
|
+
* @param {String} method The method name.
|
|
20
|
+
* @api private
|
|
21
|
+
*/
|
|
22
|
+
function context(self, method) {
|
|
23
|
+
if (self instanceof Primus) return;
|
|
24
|
+
|
|
25
|
+
var failure = new Error('Primus#'+ method + '\'s context should called with a Primus instance');
|
|
26
|
+
|
|
27
|
+
if ('function' !== typeof self.listeners || !self.listeners('error').length) {
|
|
28
|
+
throw failure;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
self.emit('error', failure);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//
|
|
35
|
+
// Sets the default connection URL, it uses the default origin of the browser
|
|
36
|
+
// when supported but degrades for older browsers. In Node.js, we cannot guess
|
|
37
|
+
// where the user wants to connect to, so we just default to localhost.
|
|
38
|
+
//
|
|
39
|
+
var defaultUrl;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
if (location.origin) {
|
|
43
|
+
defaultUrl = location.origin;
|
|
44
|
+
} else {
|
|
45
|
+
defaultUrl = location.protocol +'//'+ location.host;
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
defaultUrl = 'http://127.0.0.1';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Primus is a real-time library agnostic framework for establishing real-time
|
|
53
|
+
* connections with servers.
|
|
54
|
+
*
|
|
55
|
+
* Options:
|
|
56
|
+
* - reconnect, configuration for the reconnect process.
|
|
57
|
+
* - manual, don't automatically call `.open` to start the connection.
|
|
58
|
+
* - websockets, force the use of WebSockets, even when you should avoid them.
|
|
59
|
+
* - timeout, connect timeout, server didn't respond in a timely manner.
|
|
60
|
+
* - pingTimeout, The maximum amount of time to wait for the server to send a ping.
|
|
61
|
+
* - network, Use network events as leading method for network connection drops.
|
|
62
|
+
* - strategy, Reconnection strategies.
|
|
63
|
+
* - transport, Transport options.
|
|
64
|
+
* - url, uri, The URL to use connect with the server.
|
|
65
|
+
*
|
|
66
|
+
* @constructor
|
|
67
|
+
* @param {String} url The URL of your server.
|
|
68
|
+
* @param {Object} options The configuration.
|
|
69
|
+
* @api public
|
|
70
|
+
*/
|
|
71
|
+
function Primus(url, options) {
|
|
72
|
+
if (!(this instanceof Primus)) return new Primus(url, options);
|
|
73
|
+
|
|
74
|
+
Primus.Stream.call(this);
|
|
75
|
+
|
|
76
|
+
if ('function' !== typeof this.client) {
|
|
77
|
+
return this.critical(new Error(
|
|
78
|
+
'The client library has not been compiled correctly, see '+
|
|
79
|
+
'https://github.com/primus/primus#client-library for more details'
|
|
80
|
+
));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ('object' === typeof url) {
|
|
84
|
+
options = url;
|
|
85
|
+
url = options.url || options.uri || defaultUrl;
|
|
86
|
+
} else {
|
|
87
|
+
options = options || {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ('ping' in options || 'pong' in options) {
|
|
91
|
+
return this.critical(new Error(
|
|
92
|
+
'The `ping` and `pong` options have been removed'
|
|
93
|
+
));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
var primus = this;
|
|
97
|
+
|
|
98
|
+
// The maximum number of messages that can be placed in queue.
|
|
99
|
+
options.queueSize = 'queueSize' in options ? options.queueSize : Infinity;
|
|
100
|
+
|
|
101
|
+
// Connection timeout duration.
|
|
102
|
+
options.timeout = 'timeout' in options ? options.timeout : 10e3;
|
|
103
|
+
|
|
104
|
+
// Stores the back off configuration.
|
|
105
|
+
options.reconnect = 'reconnect' in options ? options.reconnect : {};
|
|
106
|
+
|
|
107
|
+
// Heartbeat ping interval.
|
|
108
|
+
options.pingTimeout = 'pingTimeout' in options ? options.pingTimeout : 45e3;
|
|
109
|
+
|
|
110
|
+
// Reconnect strategies.
|
|
111
|
+
options.strategy = 'strategy' in options ? options.strategy : [];
|
|
112
|
+
|
|
113
|
+
// Custom transport options.
|
|
114
|
+
options.transport = 'transport' in options ? options.transport : {};
|
|
115
|
+
|
|
116
|
+
primus.buffer = []; // Stores premature send data.
|
|
117
|
+
primus.writable = true; // Silly stream compatibility.
|
|
118
|
+
primus.readable = true; // Silly stream compatibility.
|
|
119
|
+
primus.url = primus.parse(url || defaultUrl); // Parse the URL to a readable format.
|
|
120
|
+
primus.readyState = Primus.CLOSED; // The readyState of the connection.
|
|
121
|
+
primus.options = options; // Reference to the supplied options.
|
|
122
|
+
primus.timers = new TickTock(this); // Contains all our timers.
|
|
123
|
+
primus.socket = null; // Reference to the internal connection.
|
|
124
|
+
primus.disconnect = false; // Did we receive a disconnect packet?
|
|
125
|
+
primus.transport = options.transport; // Transport options.
|
|
126
|
+
primus.transformers = { // Message transformers.
|
|
127
|
+
outgoing: [],
|
|
128
|
+
incoming: []
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
//
|
|
132
|
+
// Create our reconnection instance.
|
|
133
|
+
//
|
|
134
|
+
primus.recovery = new Recovery(options.reconnect);
|
|
135
|
+
|
|
136
|
+
//
|
|
137
|
+
// Parse the reconnection strategy. It can have the following strategies:
|
|
138
|
+
//
|
|
139
|
+
// - timeout: Reconnect when we have a network timeout.
|
|
140
|
+
// - disconnect: Reconnect when we have an unexpected disconnect.
|
|
141
|
+
// - online: Reconnect when we're back online.
|
|
142
|
+
//
|
|
143
|
+
if ('string' === typeof options.strategy) {
|
|
144
|
+
options.strategy = options.strategy.split(/\s?,\s?/g);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (false === options.strategy) {
|
|
148
|
+
//
|
|
149
|
+
// Strategies are disabled, but we still need an empty array to join it in
|
|
150
|
+
// to nothing.
|
|
151
|
+
//
|
|
152
|
+
options.strategy = [];
|
|
153
|
+
} else if (!options.strategy.length) {
|
|
154
|
+
options.strategy.push('disconnect', 'online');
|
|
155
|
+
|
|
156
|
+
//
|
|
157
|
+
// Timeout based reconnection should only be enabled conditionally. When
|
|
158
|
+
// authorization is enabled it could trigger.
|
|
159
|
+
//
|
|
160
|
+
if (!this.authorization) options.strategy.push('timeout');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
options.strategy = options.strategy.join(',').toLowerCase();
|
|
164
|
+
|
|
165
|
+
//
|
|
166
|
+
// Force the use of WebSockets, even when we've detected some potential
|
|
167
|
+
// broken WebSocket implementation.
|
|
168
|
+
//
|
|
169
|
+
if ('websockets' in options) {
|
|
170
|
+
primus.AVOID_WEBSOCKETS = !options.websockets;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//
|
|
174
|
+
// Force or disable the use of NETWORK events as leading client side
|
|
175
|
+
// disconnection detection.
|
|
176
|
+
//
|
|
177
|
+
if ('network' in options) {
|
|
178
|
+
primus.NETWORK_EVENTS = options.network;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//
|
|
182
|
+
// Check if the user wants to manually initialise a connection. If they don't,
|
|
183
|
+
// we want to do it after a really small timeout so we give the users enough
|
|
184
|
+
// time to listen for `error` events etc.
|
|
185
|
+
//
|
|
186
|
+
if (!options.manual) primus.timers.setTimeout('open', function open() {
|
|
187
|
+
primus.timers.clear('open');
|
|
188
|
+
primus.open();
|
|
189
|
+
}, 0);
|
|
190
|
+
|
|
191
|
+
primus.initialise(options);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Simple require wrapper to make browserify, node and require.js play nice.
|
|
196
|
+
*
|
|
197
|
+
* @param {String} name The module to require.
|
|
198
|
+
* @returns {Object|Undefined} The module that we required.
|
|
199
|
+
* @api private
|
|
200
|
+
*/
|
|
201
|
+
Primus.requires = Primus.require = function requires(name) {
|
|
202
|
+
if ('function' !== typeof require) return undefined;
|
|
203
|
+
|
|
204
|
+
return !('function' === typeof define && define.amd)
|
|
205
|
+
? require(name)
|
|
206
|
+
: undefined;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
//
|
|
210
|
+
// It's possible that we're running in Node.js or in a Node.js compatible
|
|
211
|
+
// environment. In this cases we try to inherit from the Stream base class.
|
|
212
|
+
//
|
|
213
|
+
try {
|
|
214
|
+
Primus.Stream = Primus.requires('stream');
|
|
215
|
+
} catch (e) { }
|
|
216
|
+
|
|
217
|
+
if (!Primus.Stream) Primus.Stream = EventEmitter;
|
|
218
|
+
|
|
219
|
+
inherits(Primus, Primus.Stream);
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Primus readyStates, used internally to set the correct ready state.
|
|
223
|
+
*
|
|
224
|
+
* @type {Number}
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
Primus.OPENING = 1; // We're opening the connection.
|
|
228
|
+
Primus.CLOSED = 2; // No active connection.
|
|
229
|
+
Primus.OPEN = 3; // The connection is open.
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Are we working with a potentially broken WebSockets implementation? This
|
|
233
|
+
* boolean can be used by transformers to remove `WebSockets` from their
|
|
234
|
+
* supported transports.
|
|
235
|
+
*
|
|
236
|
+
* @type {Boolean}
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
Primus.prototype.AVOID_WEBSOCKETS = false;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Some browsers support registering emitting `online` and `offline` events when
|
|
243
|
+
* the connection has been dropped on the client. We're going to detect it in
|
|
244
|
+
* a simple `try {} catch (e) {}` statement so we don't have to do complicated
|
|
245
|
+
* feature detection.
|
|
246
|
+
*
|
|
247
|
+
* @type {Boolean}
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
Primus.prototype.NETWORK_EVENTS = false;
|
|
251
|
+
Primus.prototype.online = true;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
if (
|
|
255
|
+
Primus.prototype.NETWORK_EVENTS = 'onLine' in navigator
|
|
256
|
+
&& (window.addEventListener || document.body.attachEvent)
|
|
257
|
+
) {
|
|
258
|
+
if (!navigator.onLine) {
|
|
259
|
+
Primus.prototype.online = false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch (e) { }
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The Ark contains all our plugins definitions. It's namespaced by
|
|
266
|
+
* name => plugin.
|
|
267
|
+
*
|
|
268
|
+
* @type {Object}
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
Primus.prototype.ark = {};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Simple emit wrapper that returns a function that emits an event once it's
|
|
275
|
+
* called. This makes it easier for transports to emit specific events.
|
|
276
|
+
*
|
|
277
|
+
* @returns {Function} A function that will emit the event when called.
|
|
278
|
+
* @api public
|
|
279
|
+
*/
|
|
280
|
+
Primus.prototype.emits = require('emits');
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Return the given plugin.
|
|
284
|
+
*
|
|
285
|
+
* @param {String} name The name of the plugin.
|
|
286
|
+
* @returns {Object|undefined} The plugin or undefined.
|
|
287
|
+
* @api public
|
|
288
|
+
*/
|
|
289
|
+
Primus.prototype.plugin = function plugin(name) {
|
|
290
|
+
context(this, 'plugin');
|
|
291
|
+
|
|
292
|
+
if (name) return this.ark[name];
|
|
293
|
+
|
|
294
|
+
var plugins = {};
|
|
295
|
+
|
|
296
|
+
for (name in this.ark) {
|
|
297
|
+
plugins[name] = this.ark[name];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return plugins;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Checks if the given event is an emitted event by Primus.
|
|
305
|
+
*
|
|
306
|
+
* @param {String} evt The event name.
|
|
307
|
+
* @returns {Boolean} Indication of the event is reserved for internal use.
|
|
308
|
+
* @api public
|
|
309
|
+
*/
|
|
310
|
+
Primus.prototype.reserved = function reserved(evt) {
|
|
311
|
+
return (/^(incoming|outgoing)::/).test(evt)
|
|
312
|
+
|| evt in this.reserved.events;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The actual events that are used by the client.
|
|
317
|
+
*
|
|
318
|
+
* @type {Object}
|
|
319
|
+
* @public
|
|
320
|
+
*/
|
|
321
|
+
Primus.prototype.reserved.events = {
|
|
322
|
+
'reconnect scheduled': 1,
|
|
323
|
+
'reconnect timeout': 1,
|
|
324
|
+
'readyStateChange': 1,
|
|
325
|
+
'reconnect failed': 1,
|
|
326
|
+
'reconnected': 1,
|
|
327
|
+
'reconnect': 1,
|
|
328
|
+
'offline': 1,
|
|
329
|
+
'timeout': 1,
|
|
330
|
+
'destroy': 1,
|
|
331
|
+
'online': 1,
|
|
332
|
+
'error': 1,
|
|
333
|
+
'close': 1,
|
|
334
|
+
'open': 1,
|
|
335
|
+
'data': 1,
|
|
336
|
+
'end': 1
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Initialise the Primus and setup all parsers and internal listeners.
|
|
341
|
+
*
|
|
342
|
+
* @param {Object} options The original options object.
|
|
343
|
+
* @returns {Primus}
|
|
344
|
+
* @api private
|
|
345
|
+
*/
|
|
346
|
+
Primus.prototype.initialise = function initialise(options) {
|
|
347
|
+
var primus = this;
|
|
348
|
+
|
|
349
|
+
primus.recovery
|
|
350
|
+
.on('reconnected', primus.emits('reconnected'))
|
|
351
|
+
.on('reconnect failed', primus.emits('reconnect failed', function failed(next) {
|
|
352
|
+
primus.emit('end');
|
|
353
|
+
next();
|
|
354
|
+
}))
|
|
355
|
+
.on('reconnect timeout', primus.emits('reconnect timeout'))
|
|
356
|
+
.on('reconnect scheduled', primus.emits('reconnect scheduled'))
|
|
357
|
+
.on('reconnect', primus.emits('reconnect', function reconnect(next) {
|
|
358
|
+
primus.emit('outgoing::reconnect');
|
|
359
|
+
next();
|
|
360
|
+
}));
|
|
361
|
+
|
|
362
|
+
primus.on('outgoing::open', function opening() {
|
|
363
|
+
var readyState = primus.readyState;
|
|
364
|
+
|
|
365
|
+
primus.readyState = Primus.OPENING;
|
|
366
|
+
if (readyState !== primus.readyState) {
|
|
367
|
+
primus.emit('readyStateChange', 'opening');
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
primus.on('incoming::open', function opened() {
|
|
372
|
+
var readyState = primus.readyState;
|
|
373
|
+
|
|
374
|
+
if (primus.recovery.reconnecting()) {
|
|
375
|
+
primus.recovery.reconnected();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
//
|
|
379
|
+
// The connection has been opened so we should set our state to
|
|
380
|
+
// (writ|read)able so our stream compatibility works as intended.
|
|
381
|
+
//
|
|
382
|
+
primus.writable = true;
|
|
383
|
+
primus.readable = true;
|
|
384
|
+
|
|
385
|
+
//
|
|
386
|
+
// Make sure we are flagged as `online` as we've successfully opened the
|
|
387
|
+
// connection.
|
|
388
|
+
//
|
|
389
|
+
if (!primus.online) {
|
|
390
|
+
primus.online = true;
|
|
391
|
+
primus.emit('online');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
primus.readyState = Primus.OPEN;
|
|
395
|
+
if (readyState !== primus.readyState) {
|
|
396
|
+
primus.emit('readyStateChange', 'open');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
primus.heartbeat();
|
|
400
|
+
|
|
401
|
+
if (primus.buffer.length) {
|
|
402
|
+
var data = primus.buffer.slice()
|
|
403
|
+
, length = data.length
|
|
404
|
+
, i = 0;
|
|
405
|
+
|
|
406
|
+
primus.buffer.length = 0;
|
|
407
|
+
|
|
408
|
+
for (; i < length; i++) {
|
|
409
|
+
primus._write(data[i]);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
primus.emit('open');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
primus.on('incoming::ping', function ping(time) {
|
|
417
|
+
primus.online = true;
|
|
418
|
+
primus.heartbeat();
|
|
419
|
+
primus.emit('outgoing::pong', time);
|
|
420
|
+
primus._write('primus::pong::'+ time);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
primus.on('incoming::error', function error(e) {
|
|
424
|
+
var connect = primus.timers.active('connect')
|
|
425
|
+
, err = e;
|
|
426
|
+
|
|
427
|
+
//
|
|
428
|
+
// When the error is not an Error instance we try to normalize it.
|
|
429
|
+
//
|
|
430
|
+
if ('string' === typeof e) {
|
|
431
|
+
err = new Error(e);
|
|
432
|
+
} else if (!(e instanceof Error) && 'object' === typeof e) {
|
|
433
|
+
//
|
|
434
|
+
// BrowserChannel and SockJS returns an object which contains some
|
|
435
|
+
// details of the error. In order to have a proper error we "copy" the
|
|
436
|
+
// details in an Error instance.
|
|
437
|
+
//
|
|
438
|
+
err = new Error(e.message || e.reason);
|
|
439
|
+
for (var key in e) {
|
|
440
|
+
if (Object.prototype.hasOwnProperty.call(e, key))
|
|
441
|
+
err[key] = e[key];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
//
|
|
445
|
+
// We're still doing a reconnect attempt, it could be that we failed to
|
|
446
|
+
// connect because the server was down. Failing connect attempts should
|
|
447
|
+
// always emit an `error` event instead of a `open` event.
|
|
448
|
+
//
|
|
449
|
+
//
|
|
450
|
+
if (primus.recovery.reconnecting()) return primus.recovery.reconnected(err);
|
|
451
|
+
if (primus.listeners('error').length) primus.emit('error', err);
|
|
452
|
+
|
|
453
|
+
//
|
|
454
|
+
// We received an error while connecting, this most likely the result of an
|
|
455
|
+
// unauthorized access to the server.
|
|
456
|
+
//
|
|
457
|
+
if (connect) {
|
|
458
|
+
if (~primus.options.strategy.indexOf('timeout')) {
|
|
459
|
+
primus.recovery.reconnect();
|
|
460
|
+
} else {
|
|
461
|
+
primus.end();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
primus.on('incoming::data', function message(raw) {
|
|
467
|
+
primus.decoder(raw, function decoding(err, data) {
|
|
468
|
+
//
|
|
469
|
+
// Do a "safe" emit('error') when we fail to parse a message. We don't
|
|
470
|
+
// want to throw here as listening to errors should be optional.
|
|
471
|
+
//
|
|
472
|
+
if (err) return primus.listeners('error').length && primus.emit('error', err);
|
|
473
|
+
|
|
474
|
+
//
|
|
475
|
+
// Handle all "primus::" prefixed protocol messages.
|
|
476
|
+
//
|
|
477
|
+
if (primus.protocol(data)) return;
|
|
478
|
+
primus.transforms(primus, primus, 'incoming', data, raw);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
primus.on('incoming::end', function end() {
|
|
483
|
+
var readyState = primus.readyState;
|
|
484
|
+
|
|
485
|
+
//
|
|
486
|
+
// This `end` started with the receiving of a primus::server::close packet
|
|
487
|
+
// which indicated that the user/developer on the server closed the
|
|
488
|
+
// connection and it was not a result of a network disruption. So we should
|
|
489
|
+
// kill the connection without doing a reconnect.
|
|
490
|
+
//
|
|
491
|
+
if (primus.disconnect) {
|
|
492
|
+
primus.disconnect = false;
|
|
493
|
+
|
|
494
|
+
return primus.end();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
//
|
|
498
|
+
// Always set the readyState to closed, and if we're still connecting, close
|
|
499
|
+
// the connection so we're sure that everything after this if statement block
|
|
500
|
+
// is only executed because our readyState is set to `open`.
|
|
501
|
+
//
|
|
502
|
+
primus.readyState = Primus.CLOSED;
|
|
503
|
+
if (readyState !== primus.readyState) {
|
|
504
|
+
primus.emit('readyStateChange', 'end');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (primus.timers.active('connect')) primus.end();
|
|
508
|
+
if (readyState !== Primus.OPEN) {
|
|
509
|
+
return primus.recovery.reconnecting()
|
|
510
|
+
? primus.recovery.reconnect()
|
|
511
|
+
: false;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
this.writable = false;
|
|
515
|
+
this.readable = false;
|
|
516
|
+
|
|
517
|
+
//
|
|
518
|
+
// Clear all timers in case we're not going to reconnect.
|
|
519
|
+
//
|
|
520
|
+
this.timers.clear();
|
|
521
|
+
|
|
522
|
+
//
|
|
523
|
+
// Fire the `close` event as an indication of connection disruption.
|
|
524
|
+
// This is also fired by `primus#end` so it is emitted in all cases.
|
|
525
|
+
//
|
|
526
|
+
primus.emit('close');
|
|
527
|
+
|
|
528
|
+
//
|
|
529
|
+
// The disconnect was unintentional, probably because the server has
|
|
530
|
+
// shutdown, so if the reconnection is enabled start a reconnect procedure.
|
|
531
|
+
//
|
|
532
|
+
if (~primus.options.strategy.indexOf('disconnect')) {
|
|
533
|
+
return primus.recovery.reconnect();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
primus.emit('outgoing::end');
|
|
537
|
+
primus.emit('end');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
//
|
|
541
|
+
// Setup the real-time client.
|
|
542
|
+
//
|
|
543
|
+
primus.client();
|
|
544
|
+
|
|
545
|
+
//
|
|
546
|
+
// Process the potential plugins.
|
|
547
|
+
//
|
|
548
|
+
for (var plugin in primus.ark) {
|
|
549
|
+
primus.ark[plugin].call(primus, primus, options);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//
|
|
553
|
+
// NOTE: The following code is only required if we're supporting network
|
|
554
|
+
// events as it requires access to browser globals.
|
|
555
|
+
//
|
|
556
|
+
if (!primus.NETWORK_EVENTS) return primus;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Handler for offline notifications.
|
|
560
|
+
*
|
|
561
|
+
* @api private
|
|
562
|
+
*/
|
|
563
|
+
primus.offlineHandler = function offline() {
|
|
564
|
+
if (!primus.online) return; // Already or still offline, bailout.
|
|
565
|
+
|
|
566
|
+
primus.online = false;
|
|
567
|
+
primus.emit('offline');
|
|
568
|
+
primus.end();
|
|
569
|
+
|
|
570
|
+
//
|
|
571
|
+
// It is certainly possible that we're in a reconnection loop and that the
|
|
572
|
+
// user goes offline. In this case we want to kill the existing attempt so
|
|
573
|
+
// when the user goes online, it will attempt to reconnect freshly again.
|
|
574
|
+
//
|
|
575
|
+
primus.recovery.reset();
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Handler for online notifications.
|
|
580
|
+
*
|
|
581
|
+
* @api private
|
|
582
|
+
*/
|
|
583
|
+
primus.onlineHandler = function online() {
|
|
584
|
+
if (primus.online) return; // Already or still online, bailout.
|
|
585
|
+
|
|
586
|
+
primus.online = true;
|
|
587
|
+
primus.emit('online');
|
|
588
|
+
|
|
589
|
+
if (~primus.options.strategy.indexOf('online')) {
|
|
590
|
+
primus.recovery.reconnect();
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
if (window.addEventListener) {
|
|
595
|
+
window.addEventListener('offline', primus.offlineHandler, false);
|
|
596
|
+
window.addEventListener('online', primus.onlineHandler, false);
|
|
597
|
+
} else if (document.body.attachEvent){
|
|
598
|
+
document.body.attachEvent('onoffline', primus.offlineHandler);
|
|
599
|
+
document.body.attachEvent('ononline', primus.onlineHandler);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return primus;
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Really dead simple protocol parser. We simply assume that every message that
|
|
607
|
+
* is prefixed with `primus::` could be used as some sort of protocol definition
|
|
608
|
+
* for Primus.
|
|
609
|
+
*
|
|
610
|
+
* @param {String} msg The data.
|
|
611
|
+
* @returns {Boolean} Is a protocol message.
|
|
612
|
+
* @api private
|
|
613
|
+
*/
|
|
614
|
+
Primus.prototype.protocol = function protocol(msg) {
|
|
615
|
+
if (
|
|
616
|
+
'string' !== typeof msg
|
|
617
|
+
|| msg.indexOf('primus::') !== 0
|
|
618
|
+
) return false;
|
|
619
|
+
|
|
620
|
+
var last = msg.indexOf(':', 8)
|
|
621
|
+
, value = msg.slice(last + 2);
|
|
622
|
+
|
|
623
|
+
switch (msg.slice(8, last)) {
|
|
624
|
+
case 'ping':
|
|
625
|
+
this.emit('incoming::ping', +value);
|
|
626
|
+
break;
|
|
627
|
+
|
|
628
|
+
case 'server':
|
|
629
|
+
//
|
|
630
|
+
// The server is closing the connection, forcefully disconnect so we don't
|
|
631
|
+
// reconnect again.
|
|
632
|
+
//
|
|
633
|
+
if ('close' === value) {
|
|
634
|
+
this.disconnect = true;
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
|
|
638
|
+
case 'id':
|
|
639
|
+
this.emit('incoming::id', value);
|
|
640
|
+
break;
|
|
641
|
+
|
|
642
|
+
//
|
|
643
|
+
// Unknown protocol, somebody is probably sending `primus::` prefixed
|
|
644
|
+
// messages.
|
|
645
|
+
//
|
|
646
|
+
default:
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return true;
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Execute the set of message transformers from Primus on the incoming or
|
|
655
|
+
* outgoing message.
|
|
656
|
+
* This function and it's content should be in sync with Spark#transforms in
|
|
657
|
+
* spark.js.
|
|
658
|
+
*
|
|
659
|
+
* @param {Primus} primus Reference to the Primus instance with message transformers.
|
|
660
|
+
* @param {Spark|Primus} connection Connection that receives or sends data.
|
|
661
|
+
* @param {String} type The type of message, 'incoming' or 'outgoing'.
|
|
662
|
+
* @param {Mixed} data The data to send or that has been received.
|
|
663
|
+
* @param {String} raw The raw encoded data.
|
|
664
|
+
* @returns {Primus}
|
|
665
|
+
* @api public
|
|
666
|
+
*/
|
|
667
|
+
Primus.prototype.transforms = function transforms(primus, connection, type, data, raw) {
|
|
668
|
+
var packet = { data: data }
|
|
669
|
+
, fns = primus.transformers[type];
|
|
670
|
+
|
|
671
|
+
//
|
|
672
|
+
// Iterate in series over the message transformers so we can allow optional
|
|
673
|
+
// asynchronous execution of message transformers which could for example
|
|
674
|
+
// retrieve additional data from the server, do extra decoding or even
|
|
675
|
+
// message validation.
|
|
676
|
+
//
|
|
677
|
+
(function transform(index, done) {
|
|
678
|
+
var transformer = fns[index++];
|
|
679
|
+
|
|
680
|
+
if (!transformer) return done();
|
|
681
|
+
|
|
682
|
+
if (1 === transformer.length) {
|
|
683
|
+
if (false === transformer.call(connection, packet)) {
|
|
684
|
+
//
|
|
685
|
+
// When false is returned by an incoming transformer it means that's
|
|
686
|
+
// being handled by the transformer and we should not emit the `data`
|
|
687
|
+
// event.
|
|
688
|
+
//
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return transform(index, done);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
transformer.call(connection, packet, function finished(err, arg) {
|
|
696
|
+
if (err) return connection.emit('error', err);
|
|
697
|
+
if (false === arg) return;
|
|
698
|
+
|
|
699
|
+
transform(index, done);
|
|
700
|
+
});
|
|
701
|
+
}(0, function done() {
|
|
702
|
+
//
|
|
703
|
+
// We always emit 2 arguments for the data event, the first argument is the
|
|
704
|
+
// parsed data and the second argument is the raw string that we received.
|
|
705
|
+
// This allows you, for example, to do some validation on the parsed data
|
|
706
|
+
// and then save the raw string in your database without the stringify
|
|
707
|
+
// overhead.
|
|
708
|
+
//
|
|
709
|
+
if ('incoming' === type) return connection.emit('data', packet.data, raw);
|
|
710
|
+
|
|
711
|
+
connection._write(packet.data);
|
|
712
|
+
}));
|
|
713
|
+
|
|
714
|
+
return this;
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Retrieve the current id from the server.
|
|
719
|
+
*
|
|
720
|
+
* @param {Function} fn Callback function.
|
|
721
|
+
* @returns {Primus}
|
|
722
|
+
* @api public
|
|
723
|
+
*/
|
|
724
|
+
Primus.prototype.id = function id(fn) {
|
|
725
|
+
if (this.socket && this.socket.id) return fn(this.socket.id);
|
|
726
|
+
|
|
727
|
+
this._write('primus::id::');
|
|
728
|
+
return this.once('incoming::id', fn);
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Establish a connection with the server. When this function is called we
|
|
733
|
+
* assume that we don't have any open connections. If you do call it when you
|
|
734
|
+
* have a connection open, it could cause duplicate connections.
|
|
735
|
+
*
|
|
736
|
+
* @returns {Primus}
|
|
737
|
+
* @api public
|
|
738
|
+
*/
|
|
739
|
+
Primus.prototype.open = function open() {
|
|
740
|
+
context(this, 'open');
|
|
741
|
+
|
|
742
|
+
//
|
|
743
|
+
// Only start a `connection timeout` procedure if we're not reconnecting as
|
|
744
|
+
// that shouldn't count as an initial connection. This should be started
|
|
745
|
+
// before the connection is opened to capture failing connections and kill the
|
|
746
|
+
// timeout.
|
|
747
|
+
//
|
|
748
|
+
if (!this.recovery.reconnecting() && this.options.timeout) this.timeout();
|
|
749
|
+
|
|
750
|
+
this.emit('outgoing::open');
|
|
751
|
+
return this;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Send a new message.
|
|
756
|
+
*
|
|
757
|
+
* @param {Mixed} data The data that needs to be written.
|
|
758
|
+
* @returns {Boolean} Always returns true as we don't support back pressure.
|
|
759
|
+
* @api public
|
|
760
|
+
*/
|
|
761
|
+
Primus.prototype.write = function write(data) {
|
|
762
|
+
context(this, 'write');
|
|
763
|
+
this.transforms(this, this, 'outgoing', data);
|
|
764
|
+
|
|
765
|
+
return true;
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* The actual message writer.
|
|
770
|
+
*
|
|
771
|
+
* @param {Mixed} data The message that needs to be written.
|
|
772
|
+
* @returns {Boolean} Successful write to the underlaying transport.
|
|
773
|
+
* @api private
|
|
774
|
+
*/
|
|
775
|
+
Primus.prototype._write = function write(data) {
|
|
776
|
+
var primus = this;
|
|
777
|
+
|
|
778
|
+
//
|
|
779
|
+
// The connection is closed, normally this would already be done in the
|
|
780
|
+
// `spark.write` method, but as `_write` is used internally, we should also
|
|
781
|
+
// add the same check here to prevent potential crashes by writing to a dead
|
|
782
|
+
// socket.
|
|
783
|
+
//
|
|
784
|
+
if (Primus.OPEN !== primus.readyState) {
|
|
785
|
+
//
|
|
786
|
+
// If the buffer is at capacity, remove the first item.
|
|
787
|
+
//
|
|
788
|
+
if (this.buffer.length === this.options.queueSize) {
|
|
789
|
+
this.buffer.splice(0, 1);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
this.buffer.push(data);
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
primus.encoder(data, function encoded(err, packet) {
|
|
797
|
+
//
|
|
798
|
+
// Do a "safe" emit('error') when we fail to parse a message. We don't
|
|
799
|
+
// want to throw here as listening to errors should be optional.
|
|
800
|
+
//
|
|
801
|
+
if (err) return primus.listeners('error').length && primus.emit('error', err);
|
|
802
|
+
|
|
803
|
+
//
|
|
804
|
+
// Hack 1: \u2028 and \u2029 are allowed inside a JSON string, but JavaScript
|
|
805
|
+
// defines them as newline separators. Unescaped control characters are not
|
|
806
|
+
// allowed inside JSON strings, so this causes an error at parse time. We
|
|
807
|
+
// work around this issue by escaping these characters. This can cause
|
|
808
|
+
// errors with JSONP requests or if the string is just evaluated.
|
|
809
|
+
//
|
|
810
|
+
if ('string' === typeof packet) {
|
|
811
|
+
if (~packet.indexOf('\u2028')) packet = packet.replace(u2028, '\\u2028');
|
|
812
|
+
if (~packet.indexOf('\u2029')) packet = packet.replace(u2029, '\\u2029');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
primus.emit('outgoing::data', packet);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
return true;
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Set a timer that, upon expiration, closes the client.
|
|
823
|
+
*
|
|
824
|
+
* @returns {Primus}
|
|
825
|
+
* @api private
|
|
826
|
+
*/
|
|
827
|
+
Primus.prototype.heartbeat = function heartbeat() {
|
|
828
|
+
if (!this.options.pingTimeout) return this;
|
|
829
|
+
|
|
830
|
+
this.timers.clear('heartbeat');
|
|
831
|
+
this.timers.setTimeout('heartbeat', function expired() {
|
|
832
|
+
//
|
|
833
|
+
// The network events already captured the offline event.
|
|
834
|
+
//
|
|
835
|
+
if (!this.online) return;
|
|
836
|
+
|
|
837
|
+
this.online = false;
|
|
838
|
+
this.emit('offline');
|
|
839
|
+
this.emit('incoming::end');
|
|
840
|
+
}, this.options.pingTimeout);
|
|
841
|
+
|
|
842
|
+
return this;
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Start a connection timeout.
|
|
847
|
+
*
|
|
848
|
+
* @returns {Primus}
|
|
849
|
+
* @api private
|
|
850
|
+
*/
|
|
851
|
+
Primus.prototype.timeout = function timeout() {
|
|
852
|
+
var primus = this;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Remove all references to the timeout listener as we've received an event
|
|
856
|
+
* that can be used to determine state.
|
|
857
|
+
*
|
|
858
|
+
* @api private
|
|
859
|
+
*/
|
|
860
|
+
function remove() {
|
|
861
|
+
primus.removeListener('error', remove)
|
|
862
|
+
.removeListener('open', remove)
|
|
863
|
+
.removeListener('end', remove)
|
|
864
|
+
.timers.clear('connect');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
primus.timers.setTimeout('connect', function expired() {
|
|
868
|
+
remove(); // Clean up old references.
|
|
869
|
+
|
|
870
|
+
if (primus.readyState === Primus.OPEN || primus.recovery.reconnecting()) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
primus.emit('timeout');
|
|
875
|
+
|
|
876
|
+
//
|
|
877
|
+
// We failed to connect to the server.
|
|
878
|
+
//
|
|
879
|
+
if (~primus.options.strategy.indexOf('timeout')) {
|
|
880
|
+
primus.recovery.reconnect();
|
|
881
|
+
} else {
|
|
882
|
+
primus.end();
|
|
883
|
+
}
|
|
884
|
+
}, primus.options.timeout);
|
|
885
|
+
|
|
886
|
+
return primus.on('error', remove)
|
|
887
|
+
.on('open', remove)
|
|
888
|
+
.on('end', remove);
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Close the connection completely.
|
|
893
|
+
*
|
|
894
|
+
* @param {Mixed} data last packet of data.
|
|
895
|
+
* @returns {Primus}
|
|
896
|
+
* @api public
|
|
897
|
+
*/
|
|
898
|
+
Primus.prototype.end = function end(data) {
|
|
899
|
+
context(this, 'end');
|
|
900
|
+
|
|
901
|
+
if (
|
|
902
|
+
this.readyState === Primus.CLOSED
|
|
903
|
+
&& !this.timers.active('connect')
|
|
904
|
+
&& !this.timers.active('open')
|
|
905
|
+
) {
|
|
906
|
+
//
|
|
907
|
+
// If we are reconnecting stop the reconnection procedure.
|
|
908
|
+
//
|
|
909
|
+
if (this.recovery.reconnecting()) {
|
|
910
|
+
this.recovery.reset();
|
|
911
|
+
this.emit('end');
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
return this;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (data !== undefined) this.write(data);
|
|
918
|
+
|
|
919
|
+
this.writable = false;
|
|
920
|
+
this.readable = false;
|
|
921
|
+
|
|
922
|
+
var readyState = this.readyState;
|
|
923
|
+
this.readyState = Primus.CLOSED;
|
|
924
|
+
|
|
925
|
+
if (readyState !== this.readyState) {
|
|
926
|
+
this.emit('readyStateChange', 'end');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
this.timers.clear();
|
|
930
|
+
this.emit('outgoing::end');
|
|
931
|
+
this.emit('close');
|
|
932
|
+
this.emit('end');
|
|
933
|
+
|
|
934
|
+
return this;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Completely demolish the Primus instance and forcefully nuke all references.
|
|
939
|
+
*
|
|
940
|
+
* @returns {Boolean}
|
|
941
|
+
* @api public
|
|
942
|
+
*/
|
|
943
|
+
Primus.prototype.destroy = destroy('url timers options recovery socket transport transformers', {
|
|
944
|
+
before: 'end',
|
|
945
|
+
after: ['removeAllListeners', function detach() {
|
|
946
|
+
if (!this.NETWORK_EVENTS) return;
|
|
947
|
+
|
|
948
|
+
if (window.addEventListener) {
|
|
949
|
+
window.removeEventListener('offline', this.offlineHandler);
|
|
950
|
+
window.removeEventListener('online', this.onlineHandler);
|
|
951
|
+
} else if (document.body.attachEvent){
|
|
952
|
+
document.body.detachEvent('onoffline', this.offlineHandler);
|
|
953
|
+
document.body.detachEvent('ononline', this.onlineHandler);
|
|
954
|
+
}
|
|
955
|
+
}]
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Create a shallow clone of a given object.
|
|
960
|
+
*
|
|
961
|
+
* @param {Object} obj The object that needs to be cloned.
|
|
962
|
+
* @returns {Object} Copy.
|
|
963
|
+
* @api private
|
|
964
|
+
*/
|
|
965
|
+
Primus.prototype.clone = function clone(obj) {
|
|
966
|
+
return this.merge({}, obj);
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Merge different objects in to one target object.
|
|
971
|
+
*
|
|
972
|
+
* @param {Object} target The object where everything should be merged in.
|
|
973
|
+
* @returns {Object} Original target with all merged objects.
|
|
974
|
+
* @api private
|
|
975
|
+
*/
|
|
976
|
+
Primus.prototype.merge = function merge(target) {
|
|
977
|
+
for (var i = 1, key, obj; i < arguments.length; i++) {
|
|
978
|
+
obj = arguments[i];
|
|
979
|
+
|
|
980
|
+
for (key in obj) {
|
|
981
|
+
if (Object.prototype.hasOwnProperty.call(obj, key))
|
|
982
|
+
target[key] = obj[key];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return target;
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Parse the connection string.
|
|
991
|
+
*
|
|
992
|
+
* @type {Function}
|
|
993
|
+
* @param {String} url Connection URL.
|
|
994
|
+
* @returns {Object} Parsed connection.
|
|
995
|
+
* @api private
|
|
996
|
+
*/
|
|
997
|
+
Primus.prototype.parse = require('url-parse');
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Parse a query string.
|
|
1001
|
+
*
|
|
1002
|
+
* @param {String} query The query string that needs to be parsed.
|
|
1003
|
+
* @returns {Object} Parsed query string.
|
|
1004
|
+
* @api private
|
|
1005
|
+
*/
|
|
1006
|
+
Primus.prototype.querystring = qs.parse;
|
|
1007
|
+
/**
|
|
1008
|
+
* Transform a query string object back into string equiv.
|
|
1009
|
+
*
|
|
1010
|
+
* @param {Object} obj The query string object.
|
|
1011
|
+
* @returns {String}
|
|
1012
|
+
* @api private
|
|
1013
|
+
*/
|
|
1014
|
+
Primus.prototype.querystringify = qs.stringify;
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Generates a connection URI.
|
|
1018
|
+
*
|
|
1019
|
+
* @param {String} protocol The protocol that should used to crate the URI.
|
|
1020
|
+
* @returns {String|options} The URL.
|
|
1021
|
+
* @api private
|
|
1022
|
+
*/
|
|
1023
|
+
Primus.prototype.uri = function uri(options) {
|
|
1024
|
+
var url = this.url
|
|
1025
|
+
, server = []
|
|
1026
|
+
, qsa = false;
|
|
1027
|
+
|
|
1028
|
+
//
|
|
1029
|
+
// Query strings are only allowed when we've received clearance for it.
|
|
1030
|
+
//
|
|
1031
|
+
if (options.query) qsa = true;
|
|
1032
|
+
|
|
1033
|
+
options = options || {};
|
|
1034
|
+
options.protocol = 'protocol' in options
|
|
1035
|
+
? options.protocol
|
|
1036
|
+
: 'http:';
|
|
1037
|
+
options.query = url.query && qsa
|
|
1038
|
+
? url.query.slice(1)
|
|
1039
|
+
: false;
|
|
1040
|
+
options.secure = 'secure' in options
|
|
1041
|
+
? options.secure
|
|
1042
|
+
: url.protocol === 'https:' || url.protocol === 'wss:';
|
|
1043
|
+
options.auth = 'auth' in options
|
|
1044
|
+
? options.auth
|
|
1045
|
+
: url.auth;
|
|
1046
|
+
options.pathname = 'pathname' in options
|
|
1047
|
+
? options.pathname
|
|
1048
|
+
: this.pathname;
|
|
1049
|
+
options.port = 'port' in options
|
|
1050
|
+
? +options.port
|
|
1051
|
+
: +url.port || (options.secure ? 443 : 80);
|
|
1052
|
+
|
|
1053
|
+
//
|
|
1054
|
+
// We need to make sure that we create a unique connection URL every time to
|
|
1055
|
+
// prevent back forward cache from becoming an issue. We're doing this by
|
|
1056
|
+
// forcing an cache busting query string in to the URL.
|
|
1057
|
+
//
|
|
1058
|
+
var querystring = this.querystring(options.query || '');
|
|
1059
|
+
querystring._primuscb = yeast();
|
|
1060
|
+
options.query = this.querystringify(querystring);
|
|
1061
|
+
|
|
1062
|
+
//
|
|
1063
|
+
// Allow transformation of the options before we construct a full URL from it.
|
|
1064
|
+
//
|
|
1065
|
+
this.emit('outgoing::url', options);
|
|
1066
|
+
|
|
1067
|
+
//
|
|
1068
|
+
// Automatically suffix the protocol so we can supply `ws:` and `http:` and
|
|
1069
|
+
// it gets transformed correctly.
|
|
1070
|
+
//
|
|
1071
|
+
server.push(options.secure ? options.protocol.replace(':', 's:') : options.protocol, '');
|
|
1072
|
+
|
|
1073
|
+
server.push(options.auth ? options.auth +'@'+ url.host : url.host);
|
|
1074
|
+
|
|
1075
|
+
//
|
|
1076
|
+
// Pathnames are optional as some Transformers would just use the pathname
|
|
1077
|
+
// directly.
|
|
1078
|
+
//
|
|
1079
|
+
if (options.pathname) server.push(options.pathname.slice(1));
|
|
1080
|
+
|
|
1081
|
+
//
|
|
1082
|
+
// Optionally add a search query.
|
|
1083
|
+
//
|
|
1084
|
+
if (qsa) server[server.length - 1] += '?'+ options.query;
|
|
1085
|
+
else delete options.query;
|
|
1086
|
+
|
|
1087
|
+
if (options.object) return options;
|
|
1088
|
+
return server.join('/');
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Register a new message transformer. This allows you to easily manipulate incoming
|
|
1093
|
+
* and outgoing data which is particularity handy for plugins that want to send
|
|
1094
|
+
* meta data together with the messages.
|
|
1095
|
+
*
|
|
1096
|
+
* @param {String} type Incoming or outgoing
|
|
1097
|
+
* @param {Function} fn A new message transformer.
|
|
1098
|
+
* @returns {Primus}
|
|
1099
|
+
* @api public
|
|
1100
|
+
*/
|
|
1101
|
+
Primus.prototype.transform = function transform(type, fn) {
|
|
1102
|
+
context(this, 'transform');
|
|
1103
|
+
|
|
1104
|
+
if (!(type in this.transformers)) {
|
|
1105
|
+
return this.critical(new Error('Invalid transformer type'));
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
this.transformers[type].push(fn);
|
|
1109
|
+
return this;
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* A critical error has occurred, if we have an `error` listener, emit it there.
|
|
1114
|
+
* If not, throw it, so we get a stack trace + proper error message.
|
|
1115
|
+
*
|
|
1116
|
+
* @param {Error} err The critical error.
|
|
1117
|
+
* @returns {Primus}
|
|
1118
|
+
* @api private
|
|
1119
|
+
*/
|
|
1120
|
+
Primus.prototype.critical = function critical(err) {
|
|
1121
|
+
if (this.emit('error', err)) return this;
|
|
1122
|
+
|
|
1123
|
+
throw err;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Syntax sugar, adopt a Socket.IO like API.
|
|
1128
|
+
*
|
|
1129
|
+
* @param {String} url The URL we want to connect to.
|
|
1130
|
+
* @param {Object} options Connection options.
|
|
1131
|
+
* @returns {Primus}
|
|
1132
|
+
* @api public
|
|
1133
|
+
*/
|
|
1134
|
+
Primus.connect = function connect(url, options) {
|
|
1135
|
+
return new Primus(url, options);
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
//
|
|
1139
|
+
// Expose the EventEmitter so it can be re-used by wrapping libraries we're also
|
|
1140
|
+
// exposing the Stream interface.
|
|
1141
|
+
//
|
|
1142
|
+
Primus.EventEmitter = EventEmitter;
|
|
1143
|
+
|
|
1144
|
+
//
|
|
1145
|
+
// These libraries are automatically inserted at the server-side using the
|
|
1146
|
+
// Primus#library method.
|
|
1147
|
+
//
|
|
1148
|
+
Primus.prototype.client = null; // @import {primus::client};
|
|
1149
|
+
Primus.prototype.authorization = null; // @import {primus::auth};
|
|
1150
|
+
Primus.prototype.pathname = null; // @import {primus::pathname};
|
|
1151
|
+
Primus.prototype.encoder = null; // @import {primus::encoder};
|
|
1152
|
+
Primus.prototype.decoder = null; // @import {primus::decoder};
|
|
1153
|
+
Primus.prototype.version = null; // @import {primus::version};
|
|
1154
|
+
|
|
1155
|
+
//
|
|
1156
|
+
// Expose the library.
|
|
1157
|
+
//
|
|
1158
|
+
module.exports = Primus;
|