app/assets/javascripts/faye-authentication.js
function FayeAuthentication(client, endpoint, options) {
this._client = client;
this._endpoint = endpoint || '/faye/auth';
this._signatures = {};
this._outbox = {};
this._options = options || {};
this._options.retry_delay = this._options.retry_delay || 1000;
this._waiting_signatures = [];
this._timer = null;
this.logger = Faye.logger;
this.ERROR_LIST = ['Expired signature', 'Required argument not signed', 'Invalid signature']
}
FayeAuthentication.prototype.endpoint = function() {
return (this._endpoint);
};
FayeAuthentication.prototype.resolveWaitingSignatures = function() {
if (this._waiting_signatures.length == 0) {
return ;
}
var self = this;
var messages = [];
$.each(this._waiting_signatures, function(key, params) {
messages.push(params);
});
this._waiting_signatures = [];
messages = messages.sort(function(a, b) {
return (a.channel > b.channel);
});
$.post(self.endpoint(), {messages: messages}, function(response) {
$.each(messages, function(key, params) {
var signature = $.grep(response.signatures || [], function(e) {
return (e.channel == params.channel && e.clientId == params.clientId);
})[0];
if (typeof signature === 'undefined') {
self.logger.error('[Faye] No signature found in ajax reply for channel ' + params.channel + ' and clientId ' + params.clientId);
} else if (signature && !signature.signature) {
self.logger.error('[Faye] Error when fetching signature for channel ' + params.channel + ' and clientId ' + params.clientId + ', error was : "' + signature.error + '"');
}
FayeAuthentication.Promise.resolve(self._signatures[params.clientId][params.channel], signature ? signature.signature : null);
});
}, 'json').fail(function(xhr, textStatus, e) {
self.logger.error('[Faye] Failure when trying to fetch JWT signature for data "' + JSON.stringify(messages) + '", error was : ' + textStatus);
$.each(messages, function(key, params) {
FayeAuthentication.Promise.resolve(self._signatures[params.clientId][params.channel], null);
});
});
};
FayeAuthentication.prototype.signMessage = function(message, callback) {
var channel = message.subscription || message.channel;
var clientId = message.clientId;
var self = this;
if (!this._signatures[clientId]) {
this._signatures[clientId] = {};
}
if (this._signatures[clientId][channel]) {
this._signatures[clientId][channel].then(function(signature) {
message.signature = signature;
if (!message.retried) {
self._outbox[message.id] = {message: message, clientId: clientId};
}
callback(message);
});
} else {
var promise = self._signatures[clientId][channel] = new FayeAuthentication.Promise();
promise.then(function(signature) {
message.signature = signature;
if (!message.retried) {
self._outbox[message.id] = {message: message, clientId: clientId};
}
callback(message);
});
this._waiting_signatures.push({channel: channel, clientId: clientId});
clearTimeout(this._timer);
this._timer = setTimeout(function() {
self.resolveWaitingSignatures();
}, 200);
}
}
FayeAuthentication.prototype.outgoing = function(message, callback) {
if (this.authentication_required(message)) {
this.signMessage(message, callback);
} else {
callback(message);
}
};
FayeAuthentication.prototype.authentication_required = function(message) {
var subscription_or_channel = message.subscription || message.channel;
if (message.channel.lastIndexOf('/meta/subscribe') === 0 || message.channel.lastIndexOf('/meta/', 0) !== 0) {
if(this._options.whitelist) {
try {
return (!this._options.whitelist(subscription_or_channel));
} catch (e) {
this.logger.error("[Faye] Error caught when evaluating whitelist function : " + e.message);
}
}
return (true);
}
return (false);
};
FayeAuthentication.prototype.incoming = function(message, callback) {
var outbox_message = this._outbox[message.id];
if (outbox_message && message.error && this.ERROR_LIST.indexOf(message.error) != -1) {
var channel = outbox_message.message.subscription || outbox_message.message.channel;
this._signatures[outbox_message.clientId][channel] = null;
outbox_message.message.retried = true;
delete outbox_message.message.id;
delete this._outbox[message.id];
var self = this;
setTimeout(function() {
self._client._sendMessage(outbox_message.message, {}, callback);
}, this._options.retry_delay);
} else {
callback(message);
}
};
(function() {
'use strict';
var timeout = setTimeout, defer;
if (typeof setImmediate === 'function')
defer = function(fn) { setImmediate(fn) };
else if (typeof process === 'object' && process.nextTick)
defer = function(fn) { process.nextTick(fn) };
else
defer = function(fn) { timeout(fn, 0) };
var PENDING = 0,
FULFILLED = 1,
REJECTED = 2;
var RETURN = function(x) { return x },
THROW = function(x) { throw x };
var Promise = function(task) {
this._state = PENDING;
this._onFulfilled = [];
this._onRejected = [];
if (typeof task !== 'function') return;
var self = this;
task(function(value) { fulfill(self, value) },
function(reason) { reject(self, reason) });
};
Promise.prototype.then = function(onFulfilled, onRejected) {
var next = new Promise();
registerOnFulfilled(this, onFulfilled, next);
registerOnRejected(this, onRejected, next);
return next;
};
var registerOnFulfilled = function(promise, onFulfilled, next) {
if (typeof onFulfilled !== 'function') onFulfilled = RETURN;
var handler = function(value) { invoke(onFulfilled, value, next) };
if (promise._state === PENDING) {
promise._onFulfilled.push(handler);
} else if (promise._state === FULFILLED) {
handler(promise._value);
}
};
var registerOnRejected = function(promise, onRejected, next) {
if (typeof onRejected !== 'function') onRejected = THROW;
var handler = function(reason) { invoke(onRejected, reason, next) };
if (promise._state === PENDING) {
promise._onRejected.push(handler);
} else if (promise._state === REJECTED) {
handler(promise._reason);
}
};
var invoke = function(fn, value, next) {
defer(function() { _invoke(fn, value, next) });
};
var _invoke = function(fn, value, next) {
var outcome;
try {
outcome = fn(value);
} catch (error) {
return reject(next, error);
}
if (outcome === next) {
reject(next, new TypeError('Recursive promise chain detected'));
} else {
fulfill(next, outcome);
}
};
var fulfill = Promise.fulfill = Promise.resolve = function(promise, value) {
var called = false, type, then;
try {
type = typeof value;
then = value !== null && (type === 'function' || type === 'object') && value.then;
if (typeof then !== 'function') return _fulfill(promise, value);
then.call(value, function(v) {
if (!(called ^ (called = true))) return;
fulfill(promise, v);
}, function(r) {
if (!(called ^ (called = true))) return;
reject(promise, r);
});
} catch (error) {
if (!(called ^ (called = true))) return;
reject(promise, error);
}
};
var _fulfill = function(promise, value) {
if (promise._state !== PENDING) return;
promise._state = FULFILLED;
promise._value = value;
promise._onRejected = [];
var onFulfilled = promise._onFulfilled, fn;
while (fn = onFulfilled.shift()) fn(value);
};
var reject = Promise.reject = function(promise, reason) {
if (promise._state !== PENDING) return;
promise._state = REJECTED;
promise._reason = reason;
promise._onFulfilled = [];
var onRejected = promise._onRejected, fn;
while (fn = onRejected.shift()) fn(reason);
};
Promise.all = function(promises) {
return new Promise(function(fulfill, reject) {
var list = [],
n = promises.length,
i;
if (n === 0) return fulfill(list);
for (i = 0; i < n; i++) (function(promise, i) {
Promise.fulfilled(promise).then(function(value) {
list[i] = value;
if (--n === 0) fulfill(list);
}, reject);
})(promises[i], i);
});
};
Promise.defer = defer;
Promise.deferred = Promise.pending = function() {
var tuple = {};
tuple.promise = new Promise(function(fulfill, reject) {
tuple.fulfill = tuple.resolve = fulfill;
tuple.reject = reject;
});
return tuple;
};
Promise.fulfilled = Promise.resolved = function(value) {
return new Promise(function(fulfill, reject) { fulfill(value) });
};
Promise.rejected = function(reason) {
return new Promise(function(fulfill, reject) { reject(reason) });
};
FayeAuthentication.Promise = Promise;
})();
(function() {
'use strict';
var timeout = setTimeout, defer;
if (typeof setImmediate === 'function')
defer = function(fn) { setImmediate(fn) };
else if (typeof process === 'object' && process.nextTick)
defer = function(fn) { process.nextTick(fn) };
else
defer = function(fn) { timeout(fn, 0) };
var PENDING = 0,
FULFILLED = 1,
REJECTED = 2;
var RETURN = function(x) { return x },
THROW = function(x) { throw x };
var Promise = function(task) {
this._state = PENDING;
this._onFulfilled = [];
this._onRejected = [];
if (typeof task !== 'function') return;
var self = this;
task(function(value) { fulfill(self, value) },
function(reason) { reject(self, reason) });
};
Promise.prototype.then = function(onFulfilled, onRejected) {
var next = new Promise();
registerOnFulfilled(this, onFulfilled, next);
registerOnRejected(this, onRejected, next);
return next;
};
var registerOnFulfilled = function(promise, onFulfilled, next) {
if (typeof onFulfilled !== 'function') onFulfilled = RETURN;
var handler = function(value) { invoke(onFulfilled, value, next) };
if (promise._state === PENDING) {
promise._onFulfilled.push(handler);
} else if (promise._state === FULFILLED) {
handler(promise._value);
}
};
var registerOnRejected = function(promise, onRejected, next) {
if (typeof onRejected !== 'function') onRejected = THROW;
var handler = function(reason) { invoke(onRejected, reason, next) };
if (promise._state === PENDING) {
promise._onRejected.push(handler);
} else if (promise._state === REJECTED) {
handler(promise._reason);
}
};
var invoke = function(fn, value, next) {
defer(function() { _invoke(fn, value, next) });
};
var _invoke = function(fn, value, next) {
var outcome;
try {
outcome = fn(value);
} catch (error) {
return reject(next, error);
}
if (outcome === next) {
reject(next, new TypeError('Recursive promise chain detected'));
} else {
fulfill(next, outcome);
}
};
var fulfill = Promise.fulfill = Promise.resolve = function(promise, value) {
var called = false, type, then;
try {
type = typeof value;
then = value !== null && (type === 'function' || type === 'object') && value.then;
if (typeof then !== 'function') return _fulfill(promise, value);
then.call(value, function(v) {
if (!(called ^ (called = true))) return;
fulfill(promise, v);
}, function(r) {
if (!(called ^ (called = true))) return;
reject(promise, r);
});
} catch (error) {
if (!(called ^ (called = true))) return;
reject(promise, error);
}
};
var _fulfill = function(promise, value) {
if (promise._state !== PENDING) return;
promise._state = FULFILLED;
promise._value = value;
promise._onRejected = [];
var onFulfilled = promise._onFulfilled, fn;
while (fn = onFulfilled.shift()) fn(value);
};
var reject = Promise.reject = function(promise, reason) {
if (promise._state !== PENDING) return;
promise._state = REJECTED;
promise._reason = reason;
promise._onFulfilled = [];
var onRejected = promise._onRejected, fn;
while (fn = onRejected.shift()) fn(reason);
};
Promise.all = function(promises) {
return new Promise(function(fulfill, reject) {
var list = [],
n = promises.length,
i;
if (n === 0) return fulfill(list);
for (i = 0; i < n; i++) (function(promise, i) {
Promise.fulfilled(promise).then(function(value) {
list[i] = value;
if (--n === 0) fulfill(list);
}, reject);
})(promises[i], i);
});
};
Promise.defer = defer;
Promise.deferred = Promise.pending = function() {
var tuple = {};
tuple.promise = new Promise(function(fulfill, reject) {
tuple.fulfill = tuple.resolve = fulfill;
tuple.reject = reject;
});
return tuple;
};
Promise.fulfilled = Promise.resolved = function(value) {
return new Promise(function(fulfill, reject) { fulfill(value) });
};
Promise.rejected = function(reason) {
return new Promise(function(fulfill, reject) { reject(reason) });
};
FayeAuthentication.Promise = Promise;
})();