| 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | var Parser = require('./parser'),
|
|---|
| 4 | Pipeline = require('./pipeline');
|
|---|
| 5 |
|
|---|
| 6 | var Extensions = function() {
|
|---|
| 7 | this._rsv1 = this._rsv2 = this._rsv3 = null;
|
|---|
| 8 |
|
|---|
| 9 | this._byName = {};
|
|---|
| 10 | this._inOrder = [];
|
|---|
| 11 | this._sessions = [];
|
|---|
| 12 | this._index = {};
|
|---|
| 13 | };
|
|---|
| 14 |
|
|---|
| 15 | Extensions.MESSAGE_OPCODES = [1, 2];
|
|---|
| 16 |
|
|---|
| 17 | var instance = {
|
|---|
| 18 | add: function(ext) {
|
|---|
| 19 | if (typeof ext.name !== 'string') throw new TypeError('extension.name must be a string');
|
|---|
| 20 | if (ext.type !== 'permessage') throw new TypeError('extension.type must be "permessage"');
|
|---|
| 21 |
|
|---|
| 22 | if (typeof ext.rsv1 !== 'boolean') throw new TypeError('extension.rsv1 must be true or false');
|
|---|
| 23 | if (typeof ext.rsv2 !== 'boolean') throw new TypeError('extension.rsv2 must be true or false');
|
|---|
| 24 | if (typeof ext.rsv3 !== 'boolean') throw new TypeError('extension.rsv3 must be true or false');
|
|---|
| 25 |
|
|---|
| 26 | if (this._byName.hasOwnProperty(ext.name))
|
|---|
| 27 | throw new TypeError('An extension with name "' + ext.name + '" is already registered');
|
|---|
| 28 |
|
|---|
| 29 | this._byName[ext.name] = ext;
|
|---|
| 30 | this._inOrder.push(ext);
|
|---|
| 31 | },
|
|---|
| 32 |
|
|---|
| 33 | generateOffer: function() {
|
|---|
| 34 | var sessions = [],
|
|---|
| 35 | offer = [],
|
|---|
| 36 | index = {};
|
|---|
| 37 |
|
|---|
| 38 | this._inOrder.forEach(function(ext) {
|
|---|
| 39 | var session = ext.createClientSession();
|
|---|
| 40 | if (!session) return;
|
|---|
| 41 |
|
|---|
| 42 | var record = [ext, session];
|
|---|
| 43 | sessions.push(record);
|
|---|
| 44 | index[ext.name] = record;
|
|---|
| 45 |
|
|---|
| 46 | var offers = session.generateOffer();
|
|---|
| 47 | offers = offers ? [].concat(offers) : [];
|
|---|
| 48 |
|
|---|
| 49 | offers.forEach(function(off) {
|
|---|
| 50 | offer.push(Parser.serializeParams(ext.name, off));
|
|---|
| 51 | }, this);
|
|---|
| 52 | }, this);
|
|---|
| 53 |
|
|---|
| 54 | this._sessions = sessions;
|
|---|
| 55 | this._index = index;
|
|---|
| 56 |
|
|---|
| 57 | return offer.length > 0 ? offer.join(', ') : null;
|
|---|
| 58 | },
|
|---|
| 59 |
|
|---|
| 60 | activate: function(header) {
|
|---|
| 61 | var responses = Parser.parseHeader(header),
|
|---|
| 62 | sessions = [];
|
|---|
| 63 |
|
|---|
| 64 | responses.eachOffer(function(name, params) {
|
|---|
| 65 | var record = this._index[name];
|
|---|
| 66 |
|
|---|
| 67 | if (!record)
|
|---|
| 68 | throw new Error('Server sent an extension response for unknown extension "' + name + '"');
|
|---|
| 69 |
|
|---|
| 70 | var ext = record[0],
|
|---|
| 71 | session = record[1],
|
|---|
| 72 | reserved = this._reserved(ext);
|
|---|
| 73 |
|
|---|
| 74 | if (reserved)
|
|---|
| 75 | throw new Error('Server sent two extension responses that use the RSV' +
|
|---|
| 76 | reserved[0] + ' bit: "' +
|
|---|
| 77 | reserved[1] + '" and "' + ext.name + '"');
|
|---|
| 78 |
|
|---|
| 79 | if (session.activate(params) !== true)
|
|---|
| 80 | throw new Error('Server sent unacceptable extension parameters: ' +
|
|---|
| 81 | Parser.serializeParams(name, params));
|
|---|
| 82 |
|
|---|
| 83 | this._reserve(ext);
|
|---|
| 84 | sessions.push(record);
|
|---|
| 85 | }, this);
|
|---|
| 86 |
|
|---|
| 87 | this._sessions = sessions;
|
|---|
| 88 | this._pipeline = new Pipeline(sessions);
|
|---|
| 89 | },
|
|---|
| 90 |
|
|---|
| 91 | generateResponse: function(header) {
|
|---|
| 92 | var sessions = [],
|
|---|
| 93 | response = [],
|
|---|
| 94 | offers = Parser.parseHeader(header);
|
|---|
| 95 |
|
|---|
| 96 | this._inOrder.forEach(function(ext) {
|
|---|
| 97 | var offer = offers.byName(ext.name);
|
|---|
| 98 | if (offer.length === 0 || this._reserved(ext)) return;
|
|---|
| 99 |
|
|---|
| 100 | var session = ext.createServerSession(offer);
|
|---|
| 101 | if (!session) return;
|
|---|
| 102 |
|
|---|
| 103 | this._reserve(ext);
|
|---|
| 104 | sessions.push([ext, session]);
|
|---|
| 105 | response.push(Parser.serializeParams(ext.name, session.generateResponse()));
|
|---|
| 106 | }, this);
|
|---|
| 107 |
|
|---|
| 108 | this._sessions = sessions;
|
|---|
| 109 | this._pipeline = new Pipeline(sessions);
|
|---|
| 110 |
|
|---|
| 111 | return response.length > 0 ? response.join(', ') : null;
|
|---|
| 112 | },
|
|---|
| 113 |
|
|---|
| 114 | validFrameRsv: function(frame) {
|
|---|
| 115 | var allowed = { rsv1: false, rsv2: false, rsv3: false },
|
|---|
| 116 | ext;
|
|---|
| 117 |
|
|---|
| 118 | if (Extensions.MESSAGE_OPCODES.indexOf(frame.opcode) >= 0) {
|
|---|
| 119 | for (var i = 0, n = this._sessions.length; i < n; i++) {
|
|---|
| 120 | ext = this._sessions[i][0];
|
|---|
| 121 | allowed.rsv1 = allowed.rsv1 || ext.rsv1;
|
|---|
| 122 | allowed.rsv2 = allowed.rsv2 || ext.rsv2;
|
|---|
| 123 | allowed.rsv3 = allowed.rsv3 || ext.rsv3;
|
|---|
| 124 | }
|
|---|
| 125 | }
|
|---|
| 126 |
|
|---|
| 127 | return (allowed.rsv1 || !frame.rsv1) &&
|
|---|
| 128 | (allowed.rsv2 || !frame.rsv2) &&
|
|---|
| 129 | (allowed.rsv3 || !frame.rsv3);
|
|---|
| 130 | },
|
|---|
| 131 |
|
|---|
| 132 | processIncomingMessage: function(message, callback, context) {
|
|---|
| 133 | this._pipeline.processIncomingMessage(message, callback, context);
|
|---|
| 134 | },
|
|---|
| 135 |
|
|---|
| 136 | processOutgoingMessage: function(message, callback, context) {
|
|---|
| 137 | this._pipeline.processOutgoingMessage(message, callback, context);
|
|---|
| 138 | },
|
|---|
| 139 |
|
|---|
| 140 | close: function(callback, context) {
|
|---|
| 141 | if (!this._pipeline) return callback.call(context);
|
|---|
| 142 | this._pipeline.close(callback, context);
|
|---|
| 143 | },
|
|---|
| 144 |
|
|---|
| 145 | _reserve: function(ext) {
|
|---|
| 146 | this._rsv1 = this._rsv1 || (ext.rsv1 && ext.name);
|
|---|
| 147 | this._rsv2 = this._rsv2 || (ext.rsv2 && ext.name);
|
|---|
| 148 | this._rsv3 = this._rsv3 || (ext.rsv3 && ext.name);
|
|---|
| 149 | },
|
|---|
| 150 |
|
|---|
| 151 | _reserved: function(ext) {
|
|---|
| 152 | if (this._rsv1 && ext.rsv1) return [1, this._rsv1];
|
|---|
| 153 | if (this._rsv2 && ext.rsv2) return [2, this._rsv2];
|
|---|
| 154 | if (this._rsv3 && ext.rsv3) return [3, this._rsv3];
|
|---|
| 155 | return false;
|
|---|
| 156 | }
|
|---|
| 157 | };
|
|---|
| 158 |
|
|---|
| 159 | for (var key in instance)
|
|---|
| 160 | Extensions.prototype[key] = instance[key];
|
|---|
| 161 |
|
|---|
| 162 | module.exports = Extensions;
|
|---|