163 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| var Parser   = require('./parser'),
 | |
|     Pipeline = require('./pipeline');
 | |
| 
 | |
| var Extensions = function() {
 | |
|   this._rsv1 = this._rsv2 = this._rsv3 = null;
 | |
| 
 | |
|   this._byName   = {};
 | |
|   this._inOrder  = [];
 | |
|   this._sessions = [];
 | |
|   this._index    = {};
 | |
| };
 | |
| 
 | |
| Extensions.MESSAGE_OPCODES = [1, 2];
 | |
| 
 | |
| var instance = {
 | |
|   add: function(ext) {
 | |
|     if (typeof ext.name !== 'string') throw new TypeError('extension.name must be a string');
 | |
|     if (ext.type !== 'permessage') throw new TypeError('extension.type must be "permessage"');
 | |
| 
 | |
|     if (typeof ext.rsv1 !== 'boolean') throw new TypeError('extension.rsv1 must be true or false');
 | |
|     if (typeof ext.rsv2 !== 'boolean') throw new TypeError('extension.rsv2 must be true or false');
 | |
|     if (typeof ext.rsv3 !== 'boolean') throw new TypeError('extension.rsv3 must be true or false');
 | |
| 
 | |
|     if (this._byName.hasOwnProperty(ext.name))
 | |
|       throw new TypeError('An extension with name "' + ext.name + '" is already registered');
 | |
| 
 | |
|     this._byName[ext.name] = ext;
 | |
|     this._inOrder.push(ext);
 | |
|   },
 | |
| 
 | |
|   generateOffer: function() {
 | |
|     var sessions = [],
 | |
|         offer    = [],
 | |
|         index    = {};
 | |
| 
 | |
|     this._inOrder.forEach(function(ext) {
 | |
|       var session = ext.createClientSession();
 | |
|       if (!session) return;
 | |
| 
 | |
|       var record = [ext, session];
 | |
|       sessions.push(record);
 | |
|       index[ext.name] = record;
 | |
| 
 | |
|       var offers = session.generateOffer();
 | |
|       offers = offers ? [].concat(offers) : [];
 | |
| 
 | |
|       offers.forEach(function(off) {
 | |
|         offer.push(Parser.serializeParams(ext.name, off));
 | |
|       }, this);
 | |
|     }, this);
 | |
| 
 | |
|     this._sessions = sessions;
 | |
|     this._index    = index;
 | |
| 
 | |
|     return offer.length > 0 ? offer.join(', ') : null;
 | |
|   },
 | |
| 
 | |
|   activate: function(header) {
 | |
|     var responses = Parser.parseHeader(header),
 | |
|         sessions  = [];
 | |
| 
 | |
|     responses.eachOffer(function(name, params) {
 | |
|       var record = this._index[name];
 | |
| 
 | |
|       if (!record)
 | |
|         throw new Error('Server sent an extension response for unknown extension "' + name + '"');
 | |
| 
 | |
|       var ext      = record[0],
 | |
|           session  = record[1],
 | |
|           reserved = this._reserved(ext);
 | |
| 
 | |
|       if (reserved)
 | |
|         throw new Error('Server sent two extension responses that use the RSV' +
 | |
|                         reserved[0] + ' bit: "' +
 | |
|                         reserved[1] + '" and "' + ext.name + '"');
 | |
| 
 | |
|       if (session.activate(params) !== true)
 | |
|         throw new Error('Server sent unacceptable extension parameters: ' +
 | |
|                         Parser.serializeParams(name, params));
 | |
| 
 | |
|       this._reserve(ext);
 | |
|       sessions.push(record);
 | |
|     }, this);
 | |
| 
 | |
|     this._sessions = sessions;
 | |
|     this._pipeline = new Pipeline(sessions);
 | |
|   },
 | |
| 
 | |
|   generateResponse: function(header) {
 | |
|     var sessions = [],
 | |
|         response = [],
 | |
|         offers   = Parser.parseHeader(header);
 | |
| 
 | |
|     this._inOrder.forEach(function(ext) {
 | |
|       var offer = offers.byName(ext.name);
 | |
|       if (offer.length === 0 || this._reserved(ext)) return;
 | |
| 
 | |
|       var session = ext.createServerSession(offer);
 | |
|       if (!session) return;
 | |
| 
 | |
|       this._reserve(ext);
 | |
|       sessions.push([ext, session]);
 | |
|       response.push(Parser.serializeParams(ext.name, session.generateResponse()));
 | |
|     }, this);
 | |
| 
 | |
|     this._sessions = sessions;
 | |
|     this._pipeline = new Pipeline(sessions);
 | |
| 
 | |
|     return response.length > 0 ? response.join(', ') : null;
 | |
|   },
 | |
| 
 | |
|   validFrameRsv: function(frame) {
 | |
|     var allowed = { rsv1: false, rsv2: false, rsv3: false },
 | |
|         ext;
 | |
| 
 | |
|     if (Extensions.MESSAGE_OPCODES.indexOf(frame.opcode) >= 0) {
 | |
|       for (var i = 0, n = this._sessions.length; i < n; i++) {
 | |
|         ext = this._sessions[i][0];
 | |
|         allowed.rsv1 = allowed.rsv1 || ext.rsv1;
 | |
|         allowed.rsv2 = allowed.rsv2 || ext.rsv2;
 | |
|         allowed.rsv3 = allowed.rsv3 || ext.rsv3;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return (allowed.rsv1 || !frame.rsv1) &&
 | |
|            (allowed.rsv2 || !frame.rsv2) &&
 | |
|            (allowed.rsv3 || !frame.rsv3);
 | |
|   },
 | |
| 
 | |
|   processIncomingMessage: function(message, callback, context) {
 | |
|     this._pipeline.processIncomingMessage(message, callback, context);
 | |
|   },
 | |
| 
 | |
|   processOutgoingMessage: function(message, callback, context) {
 | |
|     this._pipeline.processOutgoingMessage(message, callback, context);
 | |
|   },
 | |
| 
 | |
|   close: function(callback, context) {
 | |
|     if (!this._pipeline) return callback.call(context);
 | |
|     this._pipeline.close(callback, context);
 | |
|   },
 | |
| 
 | |
|   _reserve: function(ext) {
 | |
|     this._rsv1 = this._rsv1 || (ext.rsv1 && ext.name);
 | |
|     this._rsv2 = this._rsv2 || (ext.rsv2 && ext.name);
 | |
|     this._rsv3 = this._rsv3 || (ext.rsv3 && ext.name);
 | |
|   },
 | |
| 
 | |
|   _reserved: function(ext) {
 | |
|     if (this._rsv1 && ext.rsv1) return [1, this._rsv1];
 | |
|     if (this._rsv2 && ext.rsv2) return [2, this._rsv2];
 | |
|     if (this._rsv3 && ext.rsv3) return [3, this._rsv3];
 | |
|     return false;
 | |
|   }
 | |
| };
 | |
| 
 | |
| for (var key in instance)
 | |
|   Extensions.prototype[key] = instance[key];
 | |
| 
 | |
| module.exports = Extensions;
 |