@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.
Files changed (45) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +36 -0
  3. package/changes.json +30 -0
  4. package/dist/primus.js +3663 -0
  5. package/errors.js +51 -0
  6. package/index.js +1153 -0
  7. package/middleware/access-control.js +20 -0
  8. package/middleware/authorization.js +57 -0
  9. package/middleware/error.js +41 -0
  10. package/middleware/forwarded.js +18 -0
  11. package/middleware/no-cache.js +25 -0
  12. package/middleware/primus.js +47 -0
  13. package/middleware/spec.js +36 -0
  14. package/middleware/xss.js +28 -0
  15. package/package.json +140 -0
  16. package/parsers/binary.js +54 -0
  17. package/parsers/ejson.js +41 -0
  18. package/parsers/json.js +35 -0
  19. package/parsers/msgpack.js +55 -0
  20. package/parsers.json +12 -0
  21. package/primus.js +1158 -0
  22. package/spark.js +515 -0
  23. package/transformer.js +237 -0
  24. package/transformers/browserchannel/client.js +92 -0
  25. package/transformers/browserchannel/index.js +19 -0
  26. package/transformers/browserchannel/server.js +64 -0
  27. package/transformers/engine.io/README.md +34 -0
  28. package/transformers/engine.io/client.js +111 -0
  29. package/transformers/engine.io/index.js +15 -0
  30. package/transformers/engine.io/library.js +2273 -0
  31. package/transformers/engine.io/server.js +63 -0
  32. package/transformers/faye/client.js +103 -0
  33. package/transformers/faye/index.js +12 -0
  34. package/transformers/faye/server.js +85 -0
  35. package/transformers/sockjs/README.md +31 -0
  36. package/transformers/sockjs/client.js +86 -0
  37. package/transformers/sockjs/index.js +15 -0
  38. package/transformers/sockjs/library.js +4201 -0
  39. package/transformers/sockjs/server.js +108 -0
  40. package/transformers/uws/index.js +12 -0
  41. package/transformers/uws/server.js +131 -0
  42. package/transformers/websockets/client.js +112 -0
  43. package/transformers/websockets/index.js +12 -0
  44. package/transformers/websockets/server.js +77 -0
  45. package/transformers.json +26 -0
package/index.js ADDED
@@ -0,0 +1,1153 @@
1
+ 'use strict';
2
+
3
+ var PrimusError = require('./errors').PrimusError
4
+ , EventEmitter = require('eventemitter3')
5
+ , Transformer = require('./transformer')
6
+ , log = require('diagnostics')('primus')
7
+ , Spark = require('./spark')
8
+ , fuse = require('fusing')
9
+ , fs = require('fs')
10
+ , vm = require('vm');
11
+
12
+ /**
13
+ * Primus is a universal wrapper for real-time frameworks that provides a common
14
+ * interface for server and client interaction.
15
+ *
16
+ * @constructor
17
+ * @param {HTTP.Server} server HTTP or HTTPS server instance.
18
+ * @param {Object} options Configuration
19
+ * @api public
20
+ */
21
+ function Primus(server, options) {
22
+ if (!(this instanceof Primus)) return new Primus(server, options);
23
+
24
+ this.fuse();
25
+
26
+ if ('object' !== typeof server) {
27
+ var message = 'The first argument of the constructor must be ' +
28
+ 'an HTTP or HTTPS server instance';
29
+ throw new PrimusError(message, this);
30
+ }
31
+
32
+ options = options || {};
33
+ options.maxLength = options.maxLength || 10485760; // Maximum allowed packet size.
34
+ options.transport = options.transport || {}; // Transformer specific options.
35
+ options.pingInterval = 'pingInterval' in options // Heartbeat interval.
36
+ ? options.pingInterval
37
+ : 30000;
38
+
39
+ if ('timeout' in options) {
40
+ throw new PrimusError('The `timeout` option has been removed', this);
41
+ }
42
+
43
+ var primus = this
44
+ , key;
45
+
46
+ this.auth = options.authorization || null; // Do we have an authorization handler.
47
+ this.connections = Object.create(null); // Connection storage.
48
+ this.ark = Object.create(null); // Plugin storage.
49
+ this.layers = []; // Middleware layers.
50
+ this.heartbeatInterval = null; // The heartbeat interval.
51
+ this.transformer = null; // Reference to the real-time engine instance.
52
+ this.encoder = null; // Shorthand to the parser's encoder.
53
+ this.decoder = null; // Shorthand to the parser's decoder.
54
+ this.connected = 0; // Connection counter.
55
+ this.whitelist = []; // Forwarded-for white listing.
56
+ this.options = options; // The configuration.
57
+ this.transformers = { // Message transformers.
58
+ outgoing: [],
59
+ incoming: []
60
+ };
61
+
62
+ this.server = server;
63
+ this.pathname = 'string' === typeof options.pathname
64
+ ? options.pathname.charAt(0) !== '/'
65
+ ? '/'+ options.pathname
66
+ : options.pathname
67
+ : '/primus';
68
+
69
+ //
70
+ // Create a specification file with the information that people might need to
71
+ // connect to the server.
72
+ //
73
+ this.spec = {
74
+ pingInterval: options.pingInterval,
75
+ pathname: this.pathname,
76
+ version: this.version
77
+ };
78
+
79
+ //
80
+ // Create a pre-bound Spark constructor. Doing a Spark.bind(Spark, this) doesn't
81
+ // work as we cannot extend the constructor of it anymore. The added benefit of
82
+ // approach listed below is that the prototype extensions are only applied to
83
+ // the Spark of this Primus instance.
84
+ //
85
+ this.Spark = function Sparky(headers, address, query, id, request, socket) {
86
+ Spark.call(this, primus, headers, address, query, id, request, socket);
87
+ };
88
+
89
+ this.Spark.prototype = Object.create(Spark.prototype, {
90
+ constructor: {
91
+ configurable: true,
92
+ value: this.Spark,
93
+ writable: true
94
+ },
95
+ __initialise: {
96
+ value: Spark.prototype.__initialise.slice(),
97
+ configurable: true,
98
+ writable: true
99
+ }
100
+ });
101
+
102
+ //
103
+ // Copy over the original Spark static properties and methods so readable and
104
+ // writable can also be used.
105
+ //
106
+ for (key in Spark) {
107
+ this.Spark[key] = Spark[key];
108
+ }
109
+
110
+ this.parsers(options.parser);
111
+ this.initialise(options.transformer, options);
112
+
113
+ //
114
+ // If the plugins are supplied through the options, also initialise them.
115
+ // This also allows us to use plugins when creating a client constructor
116
+ // with the `Primus.createSocket({})` method.
117
+ //
118
+ if ('string' === typeof options.plugin) {
119
+ options.plugin.split(/[, ]+/).forEach(function register(name) {
120
+ primus.plugin(name, name);
121
+ });
122
+ } else if ('object' === typeof options.plugin) {
123
+ for (key in options.plugin) {
124
+ this.plugin(key, options.plugin[key]);
125
+ }
126
+ }
127
+
128
+ //
129
+ // - Cluster node 0.10 lets the Operating System decide to which worker a request
130
+ // goes. This can result in a not even distribution where some workers are
131
+ // used at 10% while others at 90%. In addition to that the load balancing
132
+ // isn't sticky.
133
+ //
134
+ // - Cluster node 0.12 implements a custom round robin algorithm. This solves the
135
+ // not even distribution of work but it does not address our sticky session
136
+ // requirement.
137
+ //
138
+ // Projects like `sticky-session` attempt to implement sticky sessions but they
139
+ // are using `net` server instead of a HTTP server in combination with the
140
+ // remoteAddress of the connection to load balance. This does not work when you
141
+ // address your servers behind a load balancer as the IP is set to the load
142
+ // balancer, not the connecting clients. All in all, it only causes more
143
+ // scalability problems. So we've opted-in to warn users about the
144
+ // risks of using Primus in a cluster.
145
+ //
146
+ if (!options.iknowclusterwillbreakconnections && require('cluster').isWorker) [
147
+ '',
148
+ 'The `cluster` module does not implement sticky sessions. Learn more about',
149
+ 'this issue at:',
150
+ '',
151
+ 'http://github.com/primus/primus#can-i-use-cluster',
152
+ ''
153
+ ].forEach(function warn(line) {
154
+ console.error('Primus: '+ line);
155
+ });
156
+ }
157
+
158
+ //
159
+ // Fuse and spice-up the Primus prototype with EventEmitter and predefine
160
+ // awesomeness.
161
+ //
162
+ fuse(Primus, EventEmitter);
163
+
164
+ //
165
+ // Lazy read the primus.js JavaScript client.
166
+ //
167
+ Object.defineProperty(Primus.prototype, 'client', {
168
+ get: function read() {
169
+ if (!read.primus) {
170
+ read.primus = fs.readFileSync(__dirname + '/dist/primus.js', 'utf-8');
171
+ }
172
+
173
+ return read.primus;
174
+ }
175
+ });
176
+
177
+ //
178
+ // Lazy compile the primus.js JavaScript client for Node.js
179
+ //
180
+ Object.defineProperty(Primus.prototype, 'Socket', {
181
+ get: function () {
182
+ const sandbox = Object.keys(global).reduce((acc, key) => {
183
+ if (key !== 'global' && key !== 'require') acc[key] = global[key];
184
+ return acc;
185
+ }, {
186
+ __dirname: process.cwd(),
187
+ __filename: 'primus.js',
188
+ require: require,
189
+
190
+ //
191
+ // The following globals are introduced so libraries that use `instanceof`
192
+ // checks for type checking do not fail as the code is run in a new
193
+ // context.
194
+ //
195
+ Uint8Array: Uint8Array,
196
+ Object: Object,
197
+ RegExp: RegExp,
198
+ Array: Array,
199
+ Error: Error,
200
+ Date: Date
201
+ });
202
+
203
+ vm.runInNewContext(this.library(true), sandbox, { filename: 'primus.js' });
204
+ return sandbox[this.options.global || 'Primus'];
205
+ }
206
+ });
207
+
208
+ //
209
+ // Expose the current version number.
210
+ //
211
+ Primus.prototype.version = require('./package.json').version;
212
+
213
+ //
214
+ // A list of supported transformers and the required Node.js modules.
215
+ //
216
+ Primus.transformers = require('./transformers.json');
217
+ Primus.parsers = require('./parsers.json');
218
+
219
+ /**
220
+ * Simple function to output common errors.
221
+ *
222
+ * @param {String} what What is missing.
223
+ * @param {Object} where Either Primus.parsers or Primus.transformers.
224
+ * @returns {Object}
225
+ * @api private
226
+ */
227
+ Primus.readable('is', function is(what, where) {
228
+ var missing = Primus.parsers !== where
229
+ ? 'transformer'
230
+ : 'parser'
231
+ , dependency = where[what];
232
+
233
+ return {
234
+ missing: function write() {
235
+ console.error('Primus:');
236
+ console.error('Primus: Missing required npm dependency for '+ what);
237
+ console.error('Primus: Please run the following command and try again:');
238
+ console.error('Primus:');
239
+ console.error('Primus: npm install --save %s', dependency.server);
240
+ console.error('Primus:');
241
+
242
+ return 'Missing dependencies for '+ missing +': "'+ what + '"';
243
+ },
244
+
245
+ unknown: function write() {
246
+ console.error('Primus:');
247
+ console.error('Primus: Unsupported %s: "%s"', missing, what);
248
+ console.error('Primus: We only support the following %ss:', missing);
249
+ console.error('Primus:');
250
+ console.error('Primus: %s', Object.keys(where).join(', '));
251
+ console.error('Primus:');
252
+
253
+ return 'Unsupported '+ missing +': "'+ what +'"';
254
+ }
255
+ };
256
+ });
257
+
258
+ /**
259
+ * Initialise the real-time engine that was chosen.
260
+ *
261
+ * @param {Mixed} Transformer The name of the transformer or a constructor;
262
+ * @param {Object} options Options.
263
+ * @api private
264
+ */
265
+ Primus.readable('initialise', function initialise(Transformer, options) {
266
+ Transformer = Transformer || 'websockets';
267
+
268
+ var primus = this
269
+ , transformer;
270
+
271
+ if ('string' === typeof Transformer) {
272
+ log('transformer `%s` is a string, attempting to resolve location', Transformer);
273
+ Transformer = transformer = Transformer.toLowerCase();
274
+ this.spec.transformer = transformer;
275
+
276
+ //
277
+ // This is a unknown transformer, it could be people made a typo.
278
+ //
279
+ if (!(Transformer in Primus.transformers)) {
280
+ log('the supplied transformer %s is not supported, please use %s', transformer, Primus.transformers);
281
+ throw new PrimusError(this.is(Transformer, Primus.transformers).unknown(), this);
282
+ }
283
+
284
+ try {
285
+ Transformer = require('./transformers/'+ transformer);
286
+ this.transformer = new Transformer(this);
287
+ } catch (e) {
288
+ if (e.code === 'MODULE_NOT_FOUND') {
289
+ log('the supplied transformer `%s` is missing', transformer);
290
+ throw new PrimusError(this.is(transformer, Primus.transformers).missing(), this);
291
+ } else {
292
+ log(e);
293
+ throw e;
294
+ }
295
+ }
296
+ } else {
297
+ log('received a custom transformer');
298
+ this.spec.transformer = 'custom';
299
+ }
300
+
301
+ if ('function' !== typeof Transformer) {
302
+ throw new PrimusError('The given transformer is not a constructor', this);
303
+ }
304
+
305
+ this.transformer = this.transformer || new Transformer(this);
306
+
307
+ this.on('connection', function connection(stream) {
308
+ this.connected++;
309
+ this.connections[stream.id] = stream;
310
+
311
+ log('connection: %s currently serving %d concurrent', stream.id, this.connected);
312
+ });
313
+
314
+ this.on('disconnection', function disconnected(stream) {
315
+ this.connected--;
316
+ delete this.connections[stream.id];
317
+
318
+ log('disconnection: %s currently serving %d concurrent', stream.id, this.connected);
319
+ });
320
+
321
+ //
322
+ // Add our default middleware layers.
323
+ //
324
+ this.use('forwarded', require('./middleware/forwarded'));
325
+ this.use('cors', require('./middleware/access-control'));
326
+ this.use('primus.js', require('./middleware/primus'));
327
+ this.use('spec', require('./middleware/spec'));
328
+ this.use('x-xss', require('./middleware/xss'));
329
+ this.use('no-cache', require('./middleware/no-cache'));
330
+ this.use('authorization', require('./middleware/authorization'));
331
+
332
+ //
333
+ // Set the heartbeat interval.
334
+ //
335
+ if (options.pingInterval) {
336
+ this.heartbeatInterval = setInterval(
337
+ this.heartbeat.bind(this),
338
+ options.pingInterval
339
+ );
340
+ }
341
+
342
+ //
343
+ // Emit the initialised event after the next tick so we have some time to
344
+ // attach listeners.
345
+ //
346
+ process.nextTick(function tock() {
347
+ primus.emit('initialised', primus.transformer, primus.parser, options);
348
+ });
349
+ });
350
+
351
+ /**
352
+ * Add a new authorization handler.
353
+ *
354
+ * @param {Function} auth The authorization handler.
355
+ * @returns {Primus}
356
+ * @api public
357
+ */
358
+ Primus.readable('authorize', function authorize(auth) {
359
+ if ('function' !== typeof auth) {
360
+ throw new PrimusError('Authorize only accepts functions', this);
361
+ }
362
+
363
+ if (auth.length < 2) {
364
+ throw new PrimusError('Authorize function requires more arguments', this);
365
+ }
366
+
367
+ log('setting an authorization function');
368
+ this.auth = auth;
369
+ return this;
370
+ });
371
+
372
+ /**
373
+ * Iterate over the connections.
374
+ *
375
+ * @param {Function} fn The function that is called every iteration.
376
+ * @param {Function} done Optional callback, if you want to iterate asynchronously.
377
+ * @returns {Primus}
378
+ * @api public
379
+ */
380
+ Primus.readable('forEach', function forEach(fn, done) {
381
+ if (!done) {
382
+ for (var id in this.connections) {
383
+ if (fn(this.spark(id), id, this.connections) === false) break;
384
+ }
385
+
386
+ return this;
387
+ }
388
+
389
+ var ids = Object.keys(this.connections)
390
+ , primus = this;
391
+
392
+ log('iterating over %d connections', ids.length);
393
+
394
+ function pushId(spark) {
395
+ ids.push(spark.id);
396
+ }
397
+
398
+ //
399
+ // We are going to iterate through the connections asynchronously so
400
+ // we should handle new connections as they come in.
401
+ //
402
+ primus.on('connection', pushId);
403
+
404
+ (function iterate() {
405
+ var id = ids.shift()
406
+ , spark;
407
+
408
+ if (!id) {
409
+ primus.removeListener('connection', pushId);
410
+ return done();
411
+ }
412
+
413
+ spark = primus.spark(id);
414
+
415
+ //
416
+ // The connection may have already been closed.
417
+ //
418
+ if (!spark) return iterate();
419
+
420
+ fn(spark, function next(err, forward) {
421
+ if (err || forward === false) {
422
+ primus.removeListener('connection', pushId);
423
+ return done(err);
424
+ }
425
+
426
+ iterate();
427
+ });
428
+ }());
429
+
430
+ return this;
431
+ });
432
+
433
+ /**
434
+ * Send a ping packet to all clients to ensure that they are still connected.
435
+ *
436
+ * @returns {Primus}
437
+ * @api private
438
+ */
439
+ Primus.readable('heartbeat', function heartbeat() {
440
+ this.forEach(function forEach(spark) {
441
+ spark.heartbeat();
442
+ });
443
+
444
+ return this;
445
+ });
446
+
447
+ /**
448
+ * Broadcast the message to all connections.
449
+ *
450
+ * @param {Mixed} data The data you want to send.
451
+ * @returns {Primus}
452
+ * @api public
453
+ */
454
+ Primus.readable('write', function write(data) {
455
+ this.forEach(function forEach(spark) {
456
+ spark.write(data);
457
+ });
458
+
459
+ return this;
460
+ });
461
+
462
+ /**
463
+ * Install message parsers.
464
+ *
465
+ * @param {Mixed} parser Parse name or parser Object.
466
+ * @returns {Primus}
467
+ * @api private
468
+ */
469
+ Primus.readable('parsers', function parsers(parser) {
470
+ parser = parser || 'json';
471
+
472
+ if ('string' === typeof parser) {
473
+ log('transformer `%s` is a string, attempting to resolve location', parser);
474
+ parser = parser.toLowerCase();
475
+ this.spec.parser = parser;
476
+
477
+ //
478
+ // This is a unknown parser, it could be people made a typo.
479
+ //
480
+ if (!(parser in Primus.parsers)) {
481
+ log('the supplied parser `%s` is not supported please use %s', parser, Primus.parsers);
482
+ throw new PrimusError(this.is(parser, Primus.parsers).unknown(), this);
483
+ }
484
+
485
+ try { parser = require('./parsers/'+ parser); }
486
+ catch (e) {
487
+ if (e.code === 'MODULE_NOT_FOUND') {
488
+ log('the supplied parser `%s` is missing', parser);
489
+ throw new PrimusError(this.is(parser, Primus.parsers).missing(), this);
490
+ } else {
491
+ log(e);
492
+ throw e;
493
+ }
494
+ }
495
+ } else {
496
+ this.spec.parser = 'custom';
497
+ }
498
+
499
+ if ('object' !== typeof parser) {
500
+ throw new PrimusError('The given parser is not an Object', this);
501
+ }
502
+
503
+ this.encoder = parser.encoder;
504
+ this.decoder = parser.decoder;
505
+ this.parser = parser;
506
+
507
+ return this;
508
+ });
509
+
510
+ /**
511
+ * Register a new message transformer. This allows you to easily manipulate incoming
512
+ * and outgoing data which is particularity handy for plugins that want to send
513
+ * meta data together with the messages.
514
+ *
515
+ * @param {String} type Incoming or outgoing
516
+ * @param {Function} fn A new message transformer.
517
+ * @returns {Primus}
518
+ * @api public
519
+ */
520
+ Primus.readable('transform', function transform(type, fn) {
521
+ if (!(type in this.transformers)) {
522
+ throw new PrimusError('Invalid transformer type', this);
523
+ }
524
+
525
+ if (~this.transformers[type].indexOf(fn)) {
526
+ log('the %s message transformer already exists, not adding it', type);
527
+ return this;
528
+ }
529
+
530
+ this.transformers[type].push(fn);
531
+ return this;
532
+ });
533
+
534
+ /**
535
+ * Gets a spark by its id.
536
+ *
537
+ * @param {String} id The spark's id.
538
+ * @returns {Spark}
539
+ * @api private
540
+ */
541
+ Primus.readable('spark', function spark(id) {
542
+ return this.connections[id];
543
+ });
544
+
545
+ /**
546
+ * Generate a client library.
547
+ *
548
+ * @param {Boolean} nodejs Don't include the library, as we're running on Node.js.
549
+ * @returns {String} The client library.
550
+ * @api public
551
+ */
552
+ Primus.readable('library', function compile(nodejs) {
553
+ var library = [ !nodejs ? this.transformer.library : null ]
554
+ , global = this.options.global || 'Primus'
555
+ , parser = this.parser.library || ''
556
+ , client = this.client;
557
+
558
+ //
559
+ // Add a simple export wrapper so it can be used as Node.js, AMD or browser
560
+ // client.
561
+ //
562
+ client = [
563
+ '(function UMDish(name, context, definition, plugins) {',
564
+ ' context[name] = definition.call(context);',
565
+ ' for (var i = 0; i < plugins.length; i++) {',
566
+ ' plugins[i](context[name])',
567
+ ' }',
568
+ ' if (typeof module !== "undefined" && module.exports) {',
569
+ ' module.exports = context[name];',
570
+ ' } else if (typeof define === "function" && define.amd) {',
571
+ ' define(function reference() { return context[name]; });',
572
+ ' }',
573
+ '})("'+ global +'", this || {}, function wrapper() {',
574
+ ' var define, module, exports',
575
+ ' , Primus = '+ client.slice(client.indexOf('return ') + 7, -4) +';',
576
+ ''
577
+ ].join('\n');
578
+
579
+ //
580
+ // Replace some basic content.
581
+ //
582
+ client = client
583
+ .replace('null; // @import {primus::pathname}', '"'+ this.pathname.toString() +'"')
584
+ .replace('null; // @import {primus::version}', '"'+ this.version +'"')
585
+ .replace('null; // @import {primus::client}', this.transformer.client.toString())
586
+ .replace('null; // @import {primus::auth}', (!!this.auth).toString())
587
+ .replace('null; // @import {primus::encoder}', this.encoder.toString())
588
+ .replace('null; // @import {primus::decoder}', this.decoder.toString());
589
+
590
+ //
591
+ // As we're given a `pingInterval` value on the server side, we need to update
592
+ // the `pingTimeout` on the client.
593
+ //
594
+ if (this.options.pingInterval) {
595
+ const value = this.options.pingInterval + Math.round(this.options.pingInterval / 2);
596
+
597
+ log('updating the default value of the client `pingTimeout` option');
598
+ client = client.replace(
599
+ 'options.pingTimeout : 45e3;',
600
+ `options.pingTimeout : ${value};`
601
+ );
602
+ } else {
603
+ log('setting the default value of the client `pingTimeout` option to `false`');
604
+ client = client.replace(
605
+ 'options.pingTimeout : 45e3;',
606
+ 'options.pingTimeout : false;'
607
+ );
608
+ }
609
+
610
+ //
611
+ // Add the parser inside the closure, to prevent global leaking.
612
+ //
613
+ if (parser && parser.length) {
614
+ log('adding parser to the client file');
615
+ client += parser;
616
+ }
617
+
618
+ //
619
+ // Iterate over the parsers, and register the client side plugins. If there's
620
+ // a library bundled, add it the library array as there were some issues with
621
+ // frameworks that get included in module wrapper as it forces strict mode.
622
+ //
623
+ var name, plugin;
624
+
625
+ for (name in this.ark) {
626
+ plugin = this.ark[name];
627
+ name = JSON.stringify(name);
628
+
629
+ if (plugin.library) {
630
+ log('adding the library of the %s plugin to the client file', name);
631
+ library.push(plugin.library);
632
+ }
633
+
634
+ if (!plugin.client) continue;
635
+
636
+ log('adding the client code of the %s plugin to the client file', name);
637
+ client += 'Primus.prototype.ark['+ name +'] = '+ plugin.client.toString() +';\n';
638
+ }
639
+
640
+ //
641
+ // Close the export wrapper and return the client. If we need to add
642
+ // a library, we should add them after we've created our closure and module
643
+ // exports. Some libraries seem to fail hard once they are wrapped in our
644
+ // closure so I'll rather expose a global variable instead of having to monkey
645
+ // patch too much code.
646
+ //
647
+ return client + [
648
+ ' return Primus;',
649
+ '},',
650
+ '['
651
+ ].concat(library.filter(Boolean).map(function expose(library) {
652
+ return [
653
+ 'function (Primus) {',
654
+ library,
655
+ '}'
656
+ ].join('\n');
657
+ }).join(',\n'))
658
+ .concat(']);')
659
+ .join('\n');
660
+ });
661
+
662
+ /**
663
+ * Save the library to disk.
664
+ *
665
+ * @param {String} dir The location that we need to save the library.
666
+ * @param {function} fn Optional callback, if you want an async save.
667
+ * @returns {Primus}
668
+ * @api public
669
+ */
670
+ Primus.readable('save', function save(path, fn) {
671
+ if (!fn) fs.writeFileSync(path, this.library(), 'utf-8');
672
+ else fs.writeFile(path, this.library(), 'utf-8', fn);
673
+
674
+ return this;
675
+ });
676
+
677
+ /**
678
+ * Register a new Primus plugin.
679
+ *
680
+ * ```js
681
+ * primus.plugin('ack', {
682
+ * //
683
+ * // Only ran on the server.
684
+ * //
685
+ * server: function (primus, options) {
686
+ * // do stuff
687
+ * },
688
+ *
689
+ * //
690
+ * // Runs on the client, it's automatically bundled.
691
+ * //
692
+ * client: function (primus, options) {
693
+ * // do client stuff
694
+ * },
695
+ *
696
+ * //
697
+ * // Optional library that needs to be bundled on the client (should be a string)
698
+ * //
699
+ * library: ''
700
+ * });
701
+ * ```
702
+ *
703
+ * @param {String} name The name of the plugin.
704
+ * @param {Object} energon The plugin that contains client and server extensions.
705
+ * @returns {Mixed}
706
+ * @api public
707
+ */
708
+ Primus.readable('plugin', function plugin(name, energon) {
709
+ if (!name) return this.ark;
710
+
711
+ if (!energon) {
712
+ if ('string' === typeof name) return this.ark[name];
713
+ if ('object' === typeof name) {
714
+ energon = name;
715
+ name = energon.name;
716
+ }
717
+ }
718
+
719
+ if ('string' !== typeof name || !name) {
720
+ throw new PrimusError('Plugin name must be a non empty string', this);
721
+ }
722
+
723
+ if ('string' === typeof energon) {
724
+ log('plugin was passed as a string, attempting to require %s', energon);
725
+ energon = require(energon);
726
+ }
727
+
728
+ //
729
+ // Plugin accepts an object or a function only.
730
+ //
731
+ if (!/^(object|function)$/.test(typeof energon)) {
732
+ throw new PrimusError('Plugin should be an object or function', this);
733
+ }
734
+
735
+ //
736
+ // Plugin require a client, server or both to be specified in the object.
737
+ //
738
+ if (!energon.server && !energon.client) {
739
+ throw new PrimusError('Plugin is missing a client or server function', this);
740
+ }
741
+
742
+ //
743
+ // Don't allow duplicate plugins or plugin override as this is most likely
744
+ // unintentional.
745
+ //
746
+ if (name in this.ark) {
747
+ throw new PrimusError('Plugin name already defined', this);
748
+ }
749
+
750
+ log('adding %s as new plugin', name);
751
+ this.ark[name] = energon;
752
+ this.emit('plugin', name, energon);
753
+
754
+ if (!energon.server) return this;
755
+
756
+ log('calling the %s plugin\'s server code', name);
757
+ energon.server.call(this, this, this.options);
758
+
759
+ return this;
760
+ });
761
+
762
+ /**
763
+ * Remove plugin from the ark.
764
+ *
765
+ * @param {String} name Name of the plugin we need to remove from the ark.
766
+ * @returns {Boolean} Successful removal of the plugin.
767
+ * @api public
768
+ */
769
+ Primus.readable('plugout', function plugout(name) {
770
+ if (!(name in this.ark)) return false;
771
+
772
+ this.emit('plugout', name, this.ark[name]);
773
+ delete this.ark[name];
774
+
775
+ return true;
776
+ });
777
+
778
+ /**
779
+ * Add a new middleware layer. If no middleware name has been provided we will
780
+ * attempt to take the name of the supplied function. If that fails, well fuck,
781
+ * just random id it.
782
+ *
783
+ * @param {String} name The name of the middleware.
784
+ * @param {Function} fn The middleware that's called each time.
785
+ * @param {Object} options Middleware configuration.
786
+ * @param {Number} level 0 based optional index for the middleware.
787
+ * @returns {Primus}
788
+ * @api public
789
+ */
790
+ Primus.readable('use', function use(name, fn, options, level) {
791
+ if ('function' === typeof name) {
792
+ level = options;
793
+ options = fn;
794
+ fn = name;
795
+ name = fn.name || 'pid_'+ Date.now();
796
+ }
797
+
798
+ if (!level && 'number' === typeof options) {
799
+ level = options;
800
+ options = {};
801
+ }
802
+
803
+ options = options || {};
804
+
805
+ //
806
+ // No or only 1 argument means that we need to initialise the middleware, this
807
+ // is a special initialisation process where we pass in a reference to the
808
+ // initialised Primus instance so a pre-compiling process can be done.
809
+ //
810
+ if (fn.length < 2) {
811
+ log('automatically configuring middleware `%s`', name);
812
+ fn = fn.call(this, options);
813
+ }
814
+
815
+ //
816
+ // Make sure that we have a function that takes at least 2 arguments.
817
+ //
818
+ if ('function' !== typeof fn || fn.length < 2) {
819
+ throw new PrimusError('Middleware should be a function that accepts at least 2 args');
820
+ }
821
+
822
+ var layer = {
823
+ length: fn.length, // Amount of arguments indicates if it's async.
824
+ enabled: true, // Middleware is enabled by default.
825
+ name: name, // Used for lookups.
826
+ fn: fn // The actual middleware.
827
+ }, index = this.indexOfLayer(name);
828
+
829
+ //
830
+ // Override middleware layer if we already have a middleware layer with
831
+ // exactly the same name.
832
+ //
833
+ if (!~index) {
834
+ if (level >= 0 && level < this.layers.length) {
835
+ log('adding middleware `%s` to the supplied index at %d', name, level);
836
+ this.layers.splice(level, 0, layer);
837
+ } else {
838
+ this.layers.push(layer);
839
+ }
840
+ } else {
841
+ this.layers[index] = layer;
842
+ }
843
+
844
+ return this;
845
+ });
846
+
847
+ /**
848
+ * Remove a middleware layer from the stack.
849
+ *
850
+ * @param {String} name The name of the middleware.
851
+ * @returns {Primus}
852
+ * @api public
853
+ */
854
+ Primus.readable('remove', function remove(name) {
855
+ var index = this.indexOfLayer(name);
856
+
857
+ if (~index) {
858
+ log('removing middleware `%s`', name);
859
+ this.layers.splice(index, 1);
860
+ }
861
+
862
+ return this;
863
+ });
864
+
865
+ /**
866
+ * Enable a given middleware layer.
867
+ *
868
+ * @param {String} name The name of the middleware.
869
+ * @returns {Primus}
870
+ * @api public
871
+ */
872
+ Primus.readable('enable', function enable(name) {
873
+ var index = this.indexOfLayer(name);
874
+
875
+ if (~index) {
876
+ log('enabling middleware `%s`', name);
877
+ this.layers[index].enabled = true;
878
+ }
879
+ return this;
880
+ });
881
+
882
+ /**
883
+ * Disable a given middleware layer.
884
+ *
885
+ * @param {String} name The name of the middleware.
886
+ * @returns {Primus}
887
+ * @api public
888
+ */
889
+ Primus.readable('disable', function disable(name) {
890
+ var index = this.indexOfLayer(name);
891
+
892
+ if (~index) {
893
+ log('disabling middleware `%s`', name);
894
+ this.layers[index].enabled = false;
895
+ }
896
+
897
+ return this;
898
+ });
899
+
900
+ /**
901
+ * Find the index of a given middleware layer by name.
902
+ *
903
+ * @param {String} name The name of the layer.
904
+ * @returns {Number}
905
+ * @api private
906
+ */
907
+ Primus.readable('indexOfLayer', function indexOfLayer(name) {
908
+ for (var i = 0, length = this.layers.length; i < length; i++) {
909
+ if (this.layers[i].name === name) return i;
910
+ }
911
+
912
+ return -1;
913
+ });
914
+
915
+ /**
916
+ * Destroy the created Primus instance.
917
+ *
918
+ * Options:
919
+ * - close (boolean) Close the given server.
920
+ * - reconnect (boolean) Trigger a client-side reconnect.
921
+ * - timeout (number) Close all active connections after x milliseconds.
922
+ *
923
+ * @param {Object} options Destruction instructions.
924
+ * @param {Function} fn Callback.
925
+ * @returns {Primus}
926
+ * @api public
927
+ */
928
+ Primus.readable('destroy', function destroy(options, fn) {
929
+ if ('function' === typeof options) {
930
+ fn = options;
931
+ options = null;
932
+ }
933
+
934
+ options = options || {};
935
+ if (options.reconnect) options.close = true;
936
+
937
+ var primus = this;
938
+
939
+ clearInterval(primus.heartbeatInterval);
940
+
941
+ setTimeout(function close() {
942
+ var transformer = primus.transformer;
943
+
944
+ //
945
+ // Ensure that the transformer receives the `close` event only once.
946
+ //
947
+ if (transformer) transformer.ultron.destroy();
948
+
949
+ //
950
+ // Close the connections that are left open.
951
+ //
952
+ primus.forEach(function shutdown(spark) {
953
+ spark.end(undefined, { reconnect: options.reconnect });
954
+ });
955
+
956
+ if (options.close !== false) {
957
+ //
958
+ // Closing a server that isn't started yet would throw an error.
959
+ //
960
+ try {
961
+ primus.server.close(function closed() {
962
+ primus.close(options, fn);
963
+ });
964
+ return;
965
+ }
966
+ catch (e) {}
967
+ }
968
+
969
+ primus.close(options, fn);
970
+ }, +options.timeout || 0);
971
+
972
+ return this;
973
+ });
974
+
975
+ /**
976
+ * Free resources after emitting a final `close` event.
977
+ *
978
+ * @param {Object} options Destruction instructions.
979
+ * @param {Function} fn Callback.
980
+ * @returns {Primus}
981
+ * @api private
982
+ */
983
+ Primus.readable('close', function close(options, fn) {
984
+ var primus = this;
985
+ //
986
+ // Emit a final `close` event before removing all the listeners
987
+ // from all the event emitters.
988
+ //
989
+ primus.asyncemit('close', options, function done(err) {
990
+ if (err) {
991
+ if (fn) return fn(err);
992
+ throw err;
993
+ }
994
+
995
+ var transformer = primus.transformer
996
+ , server = primus.server;
997
+
998
+ //
999
+ // If we don't have a server we are most likely destroying an already
1000
+ // destroyed Primus instance.
1001
+ //
1002
+ if (!server) return fn && fn();
1003
+
1004
+ server.removeAllListeners('request');
1005
+ server.removeAllListeners('upgrade');
1006
+
1007
+ //
1008
+ // Re-add the original listeners so that the server can be used again.
1009
+ //
1010
+ transformer.listeners('previous::request').forEach(function add(listener) {
1011
+ server.on('request', listener);
1012
+ });
1013
+ transformer.listeners('previous::upgrade').forEach(function add(listener) {
1014
+ server.on('upgrade', listener);
1015
+ });
1016
+
1017
+ transformer.emit('close', options);
1018
+ transformer.removeAllListeners();
1019
+
1020
+ primus.removeAllListeners();
1021
+
1022
+ //
1023
+ // Null some potentially heavy objects to free some more memory instantly.
1024
+ //
1025
+ primus.transformers.outgoing.length = primus.transformers.incoming.length = 0;
1026
+ primus.transformer = primus.encoder = primus.decoder = primus.server = null;
1027
+ primus.connected = 0;
1028
+
1029
+ primus.connections = Object.create(null);
1030
+ primus.ark = Object.create(null);
1031
+
1032
+ if (fn) fn();
1033
+ });
1034
+
1035
+ return this;
1036
+ });
1037
+
1038
+ /**
1039
+ * Async emit an event. We make a really broad assumption here and that is they
1040
+ * have the same amount of arguments as the supplied arguments (excluding the
1041
+ * event name).
1042
+ *
1043
+ * @returns {Primus}
1044
+ * @api private
1045
+ */
1046
+ Primus.readable('asyncemit', require('asyncemit'));
1047
+
1048
+ //
1049
+ // Alias for destroy.
1050
+ //
1051
+ Primus.readable('end', Primus.prototype.destroy);
1052
+
1053
+ /**
1054
+ * Checks if the given event is an emitted event by Primus.
1055
+ *
1056
+ * @param {String} evt The event name.
1057
+ * @returns {Boolean}
1058
+ * @api public
1059
+ */
1060
+ Primus.readable('reserved', function reserved(evt) {
1061
+ return (/^(incoming|outgoing)::/).test(evt)
1062
+ || evt in reserved.events;
1063
+ });
1064
+
1065
+ /**
1066
+ * The actual events that are used by Primus.
1067
+ *
1068
+ * @type {Object}
1069
+ * @api public
1070
+ */
1071
+ Primus.prototype.reserved.events = {
1072
+ 'disconnection': 1,
1073
+ 'initialised': 1,
1074
+ 'connection': 1,
1075
+ 'plugout': 1,
1076
+ 'plugin': 1,
1077
+ 'close': 1,
1078
+ 'log': 1
1079
+ };
1080
+
1081
+ /**
1082
+ * Add a createSocket interface so we can create a Server client with the
1083
+ * specified `transformer` and `parser`.
1084
+ *
1085
+ * ```js
1086
+ * var Socket = Primus.createSocket({ transformer: transformer, parser: parser })
1087
+ * , socket = new Socket(url);
1088
+ * ```
1089
+ *
1090
+ * @param {Object} options The transformer / parser we need.
1091
+ * @returns {Socket}
1092
+ * @api public
1093
+ */
1094
+ Primus.createSocket = function createSocket(options) {
1095
+ // Make sure the temporary Primus we create below doesn't start a heartbeat
1096
+ options = Object.assign({}, options, { pingInterval: false });
1097
+
1098
+ var primus = new Primus(new EventEmitter(), options);
1099
+ return primus.Socket;
1100
+ };
1101
+
1102
+ /**
1103
+ * Create a new Primus server.
1104
+ *
1105
+ * @param {Function} fn Request listener.
1106
+ * @param {Object} options Configuration.
1107
+ * @returns {Pipe}
1108
+ * @api public
1109
+ */
1110
+ Primus.createServer = function createServer(fn, options) {
1111
+ if ('object' === typeof fn) {
1112
+ options = fn;
1113
+ fn = null;
1114
+ }
1115
+
1116
+ options = options || {};
1117
+
1118
+ var server = require('create-server')(Primus.prototype.merge.call(Primus, {
1119
+ http: function warn() {
1120
+ if (!options.iknowhttpsisbetter) [
1121
+ '',
1122
+ 'We\'ve detected that you\'re using a HTTP instead of a HTTPS server.',
1123
+ 'Please be aware that real-time connections have less chance of being blocked',
1124
+ 'by firewalls and anti-virus scanners if they are encrypted (using SSL). If',
1125
+ 'you run your server behind a reverse and HTTPS terminating proxy ignore',
1126
+ 'this message, if not, you\'ve been warned.',
1127
+ ''
1128
+ ].forEach(function each(line) {
1129
+ console.log('primus: '+ line);
1130
+ });
1131
+ }
1132
+ }, options));
1133
+
1134
+ //
1135
+ // Now that we've got a server, we can setup the Primus and start listening.
1136
+ //
1137
+ var application = new Primus(server, options);
1138
+
1139
+ if (fn) application.on('connection', fn);
1140
+ return application;
1141
+ };
1142
+
1143
+ //
1144
+ // Expose the constructors of our Spark and Transformer so it can be extended by
1145
+ // a third party if needed.
1146
+ //
1147
+ Primus.Transformer = Transformer;
1148
+ Primus.Spark = Spark;
1149
+
1150
+ //
1151
+ // Expose the module.
1152
+ //
1153
+ module.exports = Primus;