gaf-mobile
Version:
GAF mobile Web site
355 lines (309 loc) • 11.8 kB
JavaScript
/* global SockJS */
(function() {
'use strict';
var build_libnotify = function() {
var socket;
var is_ready = false;
var ready_list = [];
var registered_functions = [];
var registered_filters = [];
var max_seen_packets = 5;
var seen_packets = {
'last': undefined,
'first': undefined,
'packets': {}
};
var job_channels;
var reloadedTimestamp;
var add_packet_no_delete = function(id, packet_id) {
seen_packets.packets[id] = {
'id': packet_id,
'next': undefined
};
seen_packets.packets[seen_packets.first].next = id;
seen_packets.first = id;
};
var add_packet = function(id) {
var last = seen_packets.last,
last_packet = seen_packets.packets[last],
new_last = last_packet.next;
add_packet_no_delete(id, id);
seen_packets.last = new_last;
delete seen_packets.packets[last];
};
var new_packet = function(id) {
// Packet is only considered seen if it exists
// and if it's not a placeholder
return !(seen_packets.packets[id] &&
seen_packets.packets[id].id === id);
};
(function() {
var i;
seen_packets.packets[0] = {
'id': false,
'next': undefined
};
seen_packets.first = 0;
seen_packets.last = 0;
for (i = 1; i <= max_seen_packets; i++) {
add_packet_no_delete(i, false);
}
}());
var extend = function ( defaults, options ) {
var extended = {};
var prop;
for (prop in defaults) {
if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
extended[prop] = defaults[prop];
}
}
for (prop in options) {
if (Object.prototype.hasOwnProperty.call(options, prop)) {
extended[prop] = options[prop];
}
}
return extended;
};
var message_received = function(obj) {
var key = obj.parent_type + ':' + obj.type;
if (new_packet(obj.id)) {
add_packet(obj.id);
var allowed = true;
// TODO: Register filters when user_jobs is available
// registered_filters[key].forEach(function(i, e) {
// if (e(obj) === false) {
// allowed = false;
// return false;
// }
// });
if (!allowed) {
return false;
}
if (registered_functions[key]) {
registered_functions[key].forEach(function(e) {
var obj_copy = extend({}, obj);
e(obj_copy);
});
}
}
};
var register = function(parent_type, channel, callback) {
var key = parent_type + ':' + channel;
if (registered_functions[key]) {
var callback_exists = registered_functions[key].filter(
function(cb) {
return cb === callback;
}).length !== 0;
if (!callback_exists){
registered_functions[key].push(callback);
}
} else {
registered_functions[key] = new Array(callback);
}
return this;
};
var register_filter = function(parent_type, channel, callback) {
var key = parent_type + ':' + channel;
if (registered_filters[key]) {
var callback_exists = registered_functions[key].filter(
function(cb) {
return cb === callback;
}).length !== 0;
if (!callback_exists){
registered_filters[key].push(callback);
}
} else {
registered_filters[key] = new Array(callback);
}
return this;
};
var ready = function(f, args, context){
context = context || window;
args = args || [];
if (is_ready){
f.apply(context, args);
} else {
ready_list.push({'f': f, 'args': args, 'context': context});
}
};
var set_ready = function(){
for (var i = 0; i < ready_list.length; i++){
var obj = ready_list[i];
obj.f.apply(obj.context, obj.args);
}
ready_list = [];
};
var send = function(channel, data){
socket.send(JSON.stringify({
channel: channel,
body: data
}));
};
var close_socket = function() {
if (socket) {
socket.close();
}
};
var set_job_channel = function(channels) {
if (typeof channels !== 'undefined') {
if (typeof job_channels === 'undefined' ||
(job_channels.join('') !== channels.join(''))) {
job_channels = channels;
close_socket();
}
}
};
var get_job_channel = function() {
if (typeof job_channels === 'undefined') {
job_channels = [1, 2];
}
return job_channels;
};
var Backoff = function(name, reset_time, first_timeout) {
this.name = name;
// Is this the first time its failed
this.first_fail = true;
// How long is the wait for first failure
this.first_timeout = first_timeout;
// The current failure wait time
this.wait_time = reset_time;
// The base failure wait time
this.reset_time = reset_time;
};
Backoff.prototype.fail = function() {
var delay;
if (this.first_fail) {
this.first_fail = false;
delay = this.first_timeout;
} else {
delay = this.wait_time;
this.wait_time *= 2;
this.wait_time = Math.min(this.wait_time, 60000);
}
console.log(this.name + ' Failed retrying in ' + delay);
return delay;
};
Backoff.prototype.ok = function() {
if (!this.first_fail) {
console.log(this.name + ' Recovered');
if (!reloadedTimestamp ||
reloadedTimestamp + 5000 < Date.now()) {
setTimeout(function() {
var event;
var eventString = 'libnotify.connected';
try {
event = new Event(eventString);
} catch (error) {
event = document.createEvent('Event');
event.initEvent(eventString, true, false);
}
document.dispatchEvent(event);
reloadedTimestamp = Date.now();
// Spread the request from 0 to 30 seconds
}, Math.floor(Math.random() * 30 * 1000));
}
}
this.first_fail = true;
this.wait_time = this.reset_time;
};
function getCookie(c_name) {
var c_start, c_end;
var cookies = document.cookie;
if (cookies.length>0) {
c_start = cookies.indexOf(c_name + '=');
if (c_start !== -1) {
c_start = c_start + c_name.length+1;
c_end = cookies.indexOf(';',c_start);
if (c_end === -1) {
c_end = cookies.length;
}
return cookies.substring(c_start,c_end);
}
}
return '';
}
var isLoggedIn = false;
var init = function(auth_fail_callback) {
var connect_backoff = new Backoff('sockJS Connect', 1000, 200),
reconnect_backoff = new Backoff('sockJS Reconnect', 1000, 200),
auth_backoff = new Backoff('sockJS Auth', 4000, 500);
var cookies = {
'hash': getCookie('GETAFREE_AUTH_HASH'),
'hash2': getCookie('GETAFREE_AUTH_HASH_V2'),
'user_id': getCookie('GETAFREE_USER_ID'),
'channels': get_job_channel()
};
// Dont talk to node if not loged in
if (cookies.hash2 === '' || cookies.user_id === '') {
return false;
} else {
isLoggedIn = true;
}
var new_socket = function() {
socket = new SockJS('//notifications.freelancer.com');
// Always get latest channels when socket reopens
// TODO: Enable this once user_jobs is defined in mobile web for
// posted project notifications
// cookies.channels = get_job_channel();
socket.onmessage = function(e) {
var message = JSON.parse(e.data);
if (message.channel === 'user') {
message_received(message.body);
} else if (message.channel === 'subscribe') {
reconnect_backoff.ok();
if (message.body === 'NO') {
auth_fail_callback();
socket.close(4000, 'Auth Failure');
} else {
auth_backoff.ok();
is_ready = true;
set_ready();
}
}
};
socket.onopen = function() {
connect_backoff.ok();
socket.send(JSON.stringify({
channel: 'auth',
body: cookies
}));
};
socket.onclose = function(e) {
socket = undefined;
is_ready = false;
if (e.code === 4000) {
setTimeout(new_socket, auth_backoff.fail());
} else if (!e.wasClean) {
setTimeout(new_socket, connect_backoff.fail());
} else {
setTimeout(new_socket, reconnect_backoff.fail());
}
};
};
new_socket();
};
init(function() {
console.log('auth failure');
});
// Handle the event where we come from logged out and log in
document.addEventListener('libnotify.loggedin', function() {
if (!isLoggedIn) {
init(function() {
console.log('auth failure');
});
}
});
// Register the public interface
return {
'log': true,
'register': register,
'register_filter': register_filter,
'close': close_socket,
'packets': seen_packets,
'send': send,
'ready': ready,
'set_job_channel': set_job_channel
};
};
window.libnotify = build_libnotify();
}());