(function wrapper(window, injectNeeded) {
'use strict';
// Script injection as necessary.
if ( injectNeeded ) {
var script = document.createElement('script');
script.textContent = '(' + wrapper + ')(window, false)';
document.body.appendChild(script);
document.body.removeChild(script);
return;
}
// -----------------
// Global Variables
// -----------------
var CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)[^}]*\}/mg,
IMGUR_KEY = 'e48d122e3437051', CACHE_LENGTH = 10800000,
SERVER = '//cdn.frankerfacez.com/',
DEBUG = location.search.indexOf('frankerfacez') !== -1;
// -----------------
// The Constructor
// -----------------
var ffz = function() {
this.alive = true;
this.donors = {};
this.getting = {};
// Master Emoticon Storage
this.emoticons = [];
this.emotesets = {};
// Channel Storage
this.channels = {};
// Global Sets Storage
this.collections = {};
this.globals = {};
this.global_sets = [];
this.styles = {};
// Pending Styles -- For Super Early Initialization
this.pending_styles = [];
// Keep track of all logging too.
this._log = [];
this._log2 = [];
// Now, let's do this!
this.init(10);
};
ffz.prototype.last_set = 0;
ffz.prototype.last_emote = 0;
ffz.prototype.manger = null;
ffz.prototype.has_bttv = false;
ffz.commands = {};
// -----------------
// Logging
// -----------------
ffz.prototype.log = function(msg) {
this._log.push(msg);
msg = "FFZ" + (this.alive ? ": " : " (Dead): ") + msg;
console.log(msg);
// Don't echo to chat if we're not debugging.
if ( !DEBUG ) return;
var chan;
for(var name in this.channels) {
if ( this.channels[name] && this.channels[name].room ) {
chan = this.channels[name];
break;
}
}
if ( chan )
chan.room.addTmiMessage(msg);
else
this._log2.push(msg);
};
// -----------------
// Initialization
// -----------------
ffz.prototype.init = function(increment, delay) {
// This function exists to ensure FFZ doesn't run until it can properly
// hook into the Twitch Ember application.
if ( !this.alive ) return;
var loaded = window.Ember != undefined &&
window.App != undefined &&
App.EmoticonsController != undefined &&
App.Room != undefined;
if ( !loaded ) {
// Only try loading for 60 seconds.
if ( delay >= 60000 )
this.log("Twitch API not detected in \"" + location.toString() + "\". Aborting.");
else
setTimeout(this.init.bind(this, increment, (delay||0) + increment),
increment);
return;
}
this.setup();
};
ffz.prototype.setup = function() {
if ( !this.alive ) return;
// Hook into the Ember application.
this.log("Hooking Ember application.");
this.modify_room();
this.modify_viewers();
this.modify_emotes();
this.modify_lines();
this.log("Loading data.");
this.load_donors();
this.load_emotes('global');
if ( ! document.body ) {
// We need to listen for the DOM to load in case any style elements
// get created before we can add them.
this.listen_dom = this.listen_dom.bind(this);
document.addEventListener("DOMContentLoaded", this.listen_dom, false);
}
// Detect BetterTTV
this.find_bttv(10);
this.log("Initialization complete.");
};
ffz.prototype.destroy = function() {
if ( !this.alive ) return;
// TODO: Teardown stuff.
// Mark us as dead and remove our reference.
alive = false;
if ( window.ffz === this )
window.ffz = undefined;
// And, before the door hits us... delete the log.
delete this._log;
delete this._log2;
}
// -----------------
// DOM Listening
// -----------------
ffz.prototype.listen_dom = function() {
document.removeEventListener("DOMContentLoaded", this.listen_dom, false);
// Check for waiting styles.
while ( this.pending_styles.length )
document.body.appendChild(this.pending_styles.pop());
}
// -----------------
// Commands
// -----------------
ffz.prototype._msg = function(room, out) {
if ( this.has_bttv )
return BetterTTV.chat.helpers.serverMessage(out.replace(/\n/g, "
"));
out = out.split("\n");
for(var i=0; i < out.length; i++)
room.addMessage({style: 'ffz admin', from: 'FFZ', message: out[i]});
}
ffz.prototype.run_command = function(room, m) {
var args = (m.substr(5) || "list").split(' '),
cmd = args.shift().toLowerCase();
this.log("Got FFZ Command: " + cmd + " " + JSON.stringify(args));
var c = ffz.commands[cmd], out;
if ( c )
out = c.bind(this)(room, args);
else
out = "No such sub-command.";
if ( out ) this._msg(room, out);
}
ffz.commands['help'] = function(room, args) {
if ( args && args.length > 0 ) {
var c = ffz.commands[args[0].toLowerCase()];
if ( !c )
return "No such sub-command: " + args[0];
else if ( c && c.help == undefined )
return "No help available for: " + args[0];
else
return c.help;
}
var l = [];
for (var c in ffz.commands)
ffz.commands.hasOwnProperty(c) ? l.push(c) : false;
return "Available sub-commands are: " + l.join(", ");
}
ffz.commands['help'].help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command.";
ffz.commands['log'] = function(room, args) {
var out = "FrankerFaceZ Session Log\n\n" + this._log.join("\n");
out += "\n\n--------------------------------------------------------------------------------\n" +
"Internal State\n\n";
out += "Channels:\n";
for(var id in this.channels) {
if ( !this.channels.hasOwnProperty(id) )
continue;
var chan = this.channels[id];
if ( !chan ) {
out += " " + id + " (Unloaded)\n";
continue;
}
out += " " + id + ":\n";
out += " set_id: " + chan.set_id + "\n";
if ( ! chan.set ) {
out += " set (Unloaded)\n";
} else {
out += " set:\n";
for(var i=0; i < chan.set.length; i++) {
var e = chan.set[i];
out += " isEmoticon: " + e.isEmoticon + ", cls: " + JSON.stringify(e.cls) + ", regex: " + e.regex.toString() + "\n";
}
out += "\n";
}
if ( ! chan.style) {
out += " style (Unloaded)";
} else {
var s = chan.style.innerHTML.split("\n");
out += " style:\n";
for (var i=0; i < s.length; i++)
out += " " + s[i] + "\n";
out += "\n";
}
}
out += "\nGlobal Sets:\n";
for(var id in this.globals) {
if ( !this.globals.hasOwnProperty(id) )
continue;
var set_id = this.globals[id];
if ( !set_id ) {
out += " " + id + " (Unloaded)\n";
continue;
}
out += " " + id + ":\n";
out += " set_id: " + set_id + "\n";
var set = this.emotesets[set_id];
if ( !set ) {
out += " set (Unloaded)\n";
} else {
out += " set:\n";
for(var i=0; i < set.length; i++) {
var e = set[i];
out += " isEmoticon: " + e.isEmoticon + ", cls: " + JSON.stringify(e.cls) + ", regex: " + e.regex.toString() + "\n";
}
out += "\n";
}
var style = this.styles[id];
if ( !style ) {
out += " style (Unloaded)\n";
} else {
var s = style.innerHTML.split("\n");
out += " style:\n";
for (var i=0; i < s.length; i++)
out += " " + s[i] + "\n";
out += "\n";
}
}
out += "\nEmotes:\n";
for(var i=0; i < this.emoticons.length; i++) {
var e = this.emoticons[i];
out += " " + e.text + " (" + e.image.id + ")\n";
out += " ffzset: " + e.ffzset + " (" + e.image.emoticon_set + ")\n";
out += " channel: " + e.channel + "\n";
out += " regex: " + e.regex.toString() + "\n";
out += " height: " + e.image.height + ", width: " + e.image.width + "\n";
out += " url: " + e.image.url + "\n"
out += " html: " + e.image.html + "\n\n";
}
window.open("data:text/plain," + encodeURIComponent(out), "_blank");
}
ffz.commands['log'].help = "Usage: /ffz log\nOpen a window with FFZ's debugging output.";
ffz.commands['list'] = function(room, args) {
var output = '', filter, html = this.has_bttv;
if ( args && args.length > 0 )
filter = args.join(" ").toLowerCase();
if ( html ) output += "
";
for(var name in this.collections) {
if ( ! this.collections.hasOwnProperty(name) )
return;
var include;
if ( filter )
include = name.toLowerCase().indexOf(filter) !== -1;
else
include = name !== "FFZ Global Emotes";
if ( !include )
continue;
if ( html )
output += "" + name + " | ";
else
output += name + "\n";
var em = this.collections[name];
for(var e in em) {
if ( em.hasOwnProperty(e) ) {
var emote = em[e], t = emote.text;
if ( html )
output += "" + t + " | " + emote.image.html + " |
";
else {
t = t[0] + "\u200B" + t.substr(1);
output += " " + t + " = " + emote.text + "\n";
}
}
}
if ( html ) output += "";
}
if ( html ) output += "
";
// Make sure we actually have output.
if ( output.indexOf(html ? '' : '\u200B') === -1 )
return "There are no available FFZ channel emoticons. If this is in error, please try the /ffz reload command.";
else
return "The following emotes are available:\n" + output;
}
ffz.commands['list'].help = "Usage: /ffz list [global]\nList available FFZ emoticons. Use the global parameter to list ALL FFZ emoticons, or filter for a specific set.";
ffz.commands['global'] = function(room, args) {
return ffz.commands['list'].bind(this)(room, ['global']); }
ffz.commands['global'].help = "Usage: /ffz global\nShorthand for /ffz list global. List ALL FFZ emoticons, including FFZ global emoticons.";
ffz.commands['reload'] = function(room, args) {
for(var id in this.channels)
if ( this.channels.hasOwnProperty(id) && this.channels[id] )
this.load_emotes(id, true);
this.load_emotes('global');
this.load_donors();
return "Attempting to reload FFZ data from the server.";
}
ffz.commands['reload'].help = "Usage: /ffz reload\nAttempt to reload FFZ emoticons and donors.";
ffz.commands['inject'] = function(room, args) {
if ( !args || args.length !== 1 )
return "/ffz inject requires exactly 1 argument.";
var album = args[0].split('/').pop().split('?').shift().split('#').shift();
this._msg(room, "Attempting to load test emoticons from imgur album \"" + album + "\"...");
// Make sure there's no cache hits.
var res = "https://api.imgur.com/3/album/" + album;
if ( window.localStorage )
localStorage.removeItem("ffz_" + res);
this.get(res, this.do_imgur.bind(this, room, album), 1,
{'Accept': 'application/json', 'Authorization': 'Client-ID ' + IMGUR_KEY},
5);
}
ffz.commands['inject'].help = "Usage: /ffz inject [album-id]\nLoads emoticons from an imgur album for testing. album-id can simply be the album URL. Ex: /ffz inject http://imgur.com/a/v4aZr";
ffz.prototype.do_imgur = function(room, album, data) {
if ( data === undefined )
return this._msg(room, "An error occurred communicating with Imgur.");
else if ( !data )
return this._msg(room, "The named album does not exist or is private.");
// Get our data structure.
data = JSON.parse(data).data;
var images = data.images, css = "";
for (var i=0; i < images.length; i++) {
var im = images[i],
name = im.title ? im.title : album + (i+1),
marg = im.height > 18 ? (im.height - 18) / -2 : 0,
desc = im.description ? im.description.trim().split(/(?:\W*\n\W*)+/) : undefined,
extra_css = '';
if ( desc ) {
for (var q=0; q < desc.length; q++) {
if ( desc[q].substr(0, 5).toLowerCase() === "css: " ) {
extra_css = desc[q].substr(5);
break;
}
}
}
css += ".imgur-" + album + "-" + (i+1) + ' {content: "' + name +
'"; background-image: url("' + im.link + '"); height: ' + im.height +
'px; width: ' + im.width + 'px; margin: ' + marg + 'px 0px; ' + extra_css + '}\n';
}
var count = this.process_css('imgur-' + album, 'FFZ Global Emotes - Imgur Album: ' + album, css);
this._msg(room, "Loaded " + count + " emoticons from Imgur.");
this._msg(room, ffz.commands['list'].bind(this)(room, [album]));
}
// -----------------
// BetterTTV Hooks
// -----------------
ffz.prototype.find_bttv = function(increment, delay) {
if ( !this.alive ) return;
if ( window.BTTVLOADED )
return this.setup_bttv();
else if ( delay === undefined )
this.log("BetterTTV not yet loaded. Waiting...");
if ( delay >= 60000 )
this.log("BetterTTV not detected in \"" + location.toString() + "\". Giving up.");
else
setTimeout(this.find_bttv.bind(this, increment, (delay||0) + increment),
increment);
}
var donor_badge = {type: 'ffz-donor', name: '', description: 'FFZ Donor'};
ffz.prototype.setup_bttv = function() {
this.log("BetterTTV was detected. Installing hook.");
this.has_bttv = true;
// Add badge handling to BetterTTV chat.
var privmsg = BetterTTV.chat.templates.privmsg, f = this;
BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) {
if ( f.check_donor(data.sender) ) {
var badge = _.defaults({}, donor_badge);
if ( BetterTTV.settings.get('alphaTags') )
badge['type'] = badge['type'] + ' alpha';
var inserted = false;
for(var i=0; i < data.badges.length; i++) {
var t = data.badges[i].type;
if ( t != 'turbo' && t != 'subscriber' )
continue;
data.badges.insertAt(i, badge);
inserted = true;
break;
}
if ( ! inserted )
data.badges.push(badge);
}
return privmsg(highlight, action, server, isMod, data);
}
}
// -----------------
// Ember Hooks
// -----------------
ffz.prototype.add_badge = function(sender, badges) {
// Is the sender a donor?
if ( ! this.check_donor(sender) )
return;
// Create the FFZ Donor badge.
var c = document.createElement('span');
c.className = 'badge-container tooltip';
c.setAttribute('title', 'FFZ Donor');
var b = document.createElement('div');
b.className = 'badge ffz-donor';
c.appendChild(b);
c.appendChild(document.createTextNode(' '));
// Figure out where to place the badge.
var before = badges.find('.badge-container').filter(function(i) {
var t = this.title.toLowerCase();
return t == "subscriber" || t == "turbo";
}).first();
if ( before.length )
before.before(c);
else
badges.append(c);
}
ffz.prototype.modify_lines = function() {
var f = this;
App.LineView.reopen({
didInsertElement: function() {
this._super();
f.add_badge(this.get('context.model.from'), this.$('.badges'));
}
});
}
ffz.prototype._modify_room = function(room) {
var f = this;
room.reopen({
init: function() {
this._super();
if ( f.alive )
f.add_channel(this.id, this);
},
willDestroy: function() {
this._super();
if ( f.alive )
f.remove_channel(this.id);
},
send: function(e) {
if ( f.alive && (e.substr(0,5) == '/ffz ' || e == '/ffz') ) {
// Clear the input box.
this.set("messageToSend", "");
f.run_command(this, e);
} else
return this._super(e);
}
});
}
ffz.prototype.modify_room = function() {
this._modify_room(App.Room);
var inst = App.Room.instances;
for(var n in inst) {
if ( ! inst.hasOwnProperty(n) ) continue;
var i = inst[n];
if ( this.alive )
this.add_channel(i.id, i);
if ( i.tmiRoom && this.alive )
this.alter_tmi(i.id, i.tmiRoom);
else if ( i.viewers )
this._modify_viewers(i.viewers);
this._modify_room(i);
}
};
ffz.prototype._modify_viewers = function(vwrs) {
var f = this;
vwrs.reopen({
tmiRoom: Ember.computed(function(key, val) {
if ( arguments.length > 1 ) {
this.tmiRoom = val;
if ( f.alive )
f.alter_tmi(this.id, val);
}
return undefined;
})
});
}
ffz.prototype.modify_viewers = function() {
this._modify_viewers(App.Room.Viewers);
};
ffz.prototype._modify_emotes = function(ec) {
var f = this;
ec.reopen({
_emoticons: ec.emoticons || [],
init: function() {
this._super();
if ( f.alive )
f.get_manager(this);
},
emoticons: Ember.computed(function(key, val) {
if ( arguments.length > 1 ) {
this._emoticons = val;
f.log("Twitch standard emoticons loaded.");
}
return f.alive ? _.union(this._emoticons, f.emoticons) : this._emoticons;
})
});
}
ffz.prototype.modify_emotes = function() {
this._modify_emotes(App.EmoticonsController);
var ec = App.__container__.lookup("controller:emoticons");
if ( ! ec ) return;
this._modify_emotes(ec);
this.get_manager(ec);
};
ffz.prototype.get_manager = function(manager) {
this.manager = manager;
for(var key in this.emotesets) {
if ( this.emotesets.hasOwnProperty(key) )
manager.emoticonSets[key] = this.emotesets[key];
}
}
// -----------------
// Channel Management
// -----------------
ffz.prototype.add_channel = function(id, room) {
if ( !this.alive ) return;
this.log("Registered channel: " + id);
var chan = this.channels[id] = {id: id, room: room, tmi: null, style: null};
// Do we have log messages?
if ( this._log2.length > 0 ) {
var func = this.has_bttv ? BetterTTV.chat.helpers.serverMessage : room.addTmiMessage;
while ( this._log2.length )
func(this._log2.shift());
}
// Load the emotes for this channel.
this.load_emotes(id);
}
ffz.prototype.remove_channel = function(id) {
var chan = this.channels[id];
if ( !chan ) return;
this.log("Removing channel: " + id);
// Unload the associated emotes.
this.unload_emotes(id);
// If we have a tmiRoom for this channel, restore its getEmotes function.
if ( chan.tmi )
delete chan.tmi.getEmotes;
// Delete this channel.
this.channels[id] = false;
}
ffz.prototype.alter_tmi = function(id, tmi) {
var chan = this.channels[id], f = this;
if ( !chan || !this.alive ) return;
// Store the TMI instance.
if ( chan.tmi) return;
chan.tmi = tmi;
var tp = tmi.__proto__.getEmotes.bind(tmi);
tmi.getEmotes = function(name) {
return _.union([chan.set_id], f.global_sets, tp(name)||[]);
}
}
// -----------------
// Emote Handling
// -----------------
ffz.prototype.load_emotes = function(group, refresh) {
// TEMPORARY GROUP CHAT
var m = /^_(.+)_\d+$/.exec(group), name = group;
if ( m != null )
name = m[1];
this.get(SERVER + "channel/" + name + ".css",
this.process_css.bind(this, group, undefined), refresh ? 1 : CACHE_LENGTH);
}
ffz.prototype.process_css = function(group, channel, data) {
if ( !this.alive ) return 0;
if ( data === undefined ) return 0;
// Before we go anywhere, let's start clean.
this.unload_emotes(group);
// If data is null, we've got no emotes.
if ( data == null )
return 0;
// Let's look up this group to see where it goes! Is it a channel?
var chan = this.channels[group];
if ( chan === false )
// It's for an unloaded channel. Stop here.
return;
// Get our new stuff.
var set_id = --this.last_set, set = [], channel,
style = document.createElement('style');
// Let's store our things right now.
if ( chan ) {
chan.set_id = set_id;
chan.set = set;
chan.style = style;
channel = "FFZ Channel Emotes: " + group;
} else {
this.globals[group] = set_id;
this.global_sets.push(set_id);
this.styles[group] = style;
if ( !channel )
channel = "FFZ Global Emotes" + (group != "global" ? ": " + group : "");
}
// Register this set with the manager.
this.emotesets[set_id] = set;
if ( this.manager )
this.manager.emoticonSets[set_id] = set;
// Update the style.
style.type = 'text/css';
style.innerHTML = data;
if ( document.body )
document.body.appendChild(style);
else
this.pending_styles.push(style);
// Parse out the usable emoticons.
var count = 0, f = this;
// Store our emotes in an extra place.
var col = this.collections[channel] = [];
data.replace(CSS, function(match, klass, name, path, height, width) {
height = parseInt(height); width = parseInt(width);
var image_data = {
emoticon_set: set_id, height: height, width: width, url: path,
html: '',
id: --f.last_emote}, regex;
if ( name[name.length-1] === '!' )
regex = new RegExp('\\b' + name + '(?=\\W|$)', 'g');
else
regex = new RegExp('\\b' + name + '\\b', 'g');
var emote = {
image: image_data, images: [image_data], text: name,
channel: channel, hidden: false, regex: regex, ffzset: group};
col.push(emote);
f.emoticons.push(emote);
set.push({isEmoticon: !0, cls: klass, regex: regex});
count++;
});
this.log("Loaded " + count + " emotes from collection: " + group);
// Notify the manager that we've added emotes.
// Don't notify the manager for now because of BTTV.
//if ( this.manager && ! this.has_bttv )
// this.manager.notifyPropertyChange('emoticons');
return count;
}
ffz.prototype.unload_emotes = function(group) {
if ( !this.alive ) return;
// Is it a channel?
var chan = this.channels[group], set, set_id, style, channel;
if ( chan === false )
return;
else if ( chan ) {
// It's a channel.
set = chan.set;
set_id = chan.set_id;
style = chan.style;
channel = "FFZ Channel Emotes: " + group;
// Clear it out.
delete chan.set;
delete chan.set_id;
delete chan.style;
} else {
// It must be global.
set_id = this.globals[group];
set = this.emotesets[set_id];
style = this.styles[group];
channel = "FFZ Global Emotes" + (group != "global" ? ": " + group : "");
// Clear out the basics.
delete this.globals[group];
delete this.styles[group];
var ind = this.global_sets.indexOf(set_id);
if ( ind !== -1 )
this.global_sets.splice(ind, 1);
}
// Do we have a collection?
if ( this.collections[channel] )
delete this.collections[channel];
// Do we have a style?
if ( style )
// Remove it from its parent.
try { style.parentNode.removeChild(style); } catch(err) {}
// Remove the emoteset from circulation.
delete this.emotesets[set_id];
if ( this.manager )
delete this.manager.emoticonSets[set_id];
// Remove every emote from this group.
var filt = function(e) { return e.ffzgroup !== group; }
this.emoticons = this.emoticons.filter(filt);
// Update the emoticons with the manager.
// Don't notify the manager for now for BTTV.
//if ( this.manager && ! this.has_bttv )
// this.manager.notifyPropertyChange('emoticons');
}
// -----------------
// Donor Processing
// -----------------
ffz.prototype.check_donor = function(username) { return this.donors[username] || false; }
ffz.prototype.load_donors = function(refresh) {
this.get(SERVER + "scripts/donors.txt",
this.process_donors.bind(this), refresh ? 1 : CACHE_LENGTH);
}
ffz.prototype.process_donors = function(text) {
if ( !this.alive ) return;
this.donors = {};
var count = 0;
if ( text != null ) {
var l = text.trim().split(/\W+/);
for (var i=0; i < l.length; i++)
this.donors[l[i]] = true;
count += l.length;
}
this.log("Loaded " + count + " donors.");
}
// -----------------
// Networking
// -----------------
ffz.prototype.get = function(resource, callback, expires, headers, max_attempts) {
if ( !this.alive ) return;
if ( this.getting[resource] ) {
this.log("Already getting resource: " + resource);
return;
}
this.getting[resource] = true;
max_attempts = max_attempts || 10;
var age = 0, now = new Date().getTime();
// First, immediately try using the resource from cache.
if ( window.localStorage ) {
var res = localStorage.getItem("ffz_" + resource);
if ( res != null ) {
this.log("Found resource in localStorage: " + resource);
try {
callback(JSON.parse(res));
} catch(err) { this.log("Error in callback: " + err); }
// Also, get the age to see if we need to fetch it again.
age = parseInt(localStorage.getItem("ffz_age_" + resource)||0);
}
}
if ( DEBUG || !age || (expires !== undefined && expires !== null && (now-age) > expires) ) {
// Try getting it again.
this.log("Resource expired. Fetching: " + resource);
this.do_get(resource, callback, 0, headers, max_attempts);
} else
this.getting[resource] = false;
}
ffz.prototype.do_get = function(resource, callback, attempts, headers, max_attempts) {
if ( !this.alive ) {
this.getting[resource] = false;
return;
}
var http = new XMLHttpRequest();
http.open("GET", resource);
if ( headers ) {
for (var hdr in headers) {
if ( headers.hasOwnProperty(hdr) )
http.setRequestHeader(hdr, headers[hdr]);
}
}
var f = this;
function try_again() {
var attempt = (attempts || 0) + 1, delay = 1000;
if ( !max_attempts || attempt <= max_attempts ) {
setTimeout(f.do_get.bind(f, resource, callback, attempt, headers, max_attempts), delay);
return true;
}
}
http.addEventListener("error", function(e) {
if ( try_again() )
return;
f.getting[resource] = false;
try {
callback(undefined);
} catch(err) { f.log("Error in callback: " + err); }
}, false);
http.addEventListener("load", function(e) {
var result;
if ( http.status === 200 ) {
// Success!
result = http.responseText;
// Let's see if it was modified?
if ( window.localStorage ) {
var last = localStorage.getItem("ffz_last_" + resource),
nl = http.getResponseHeader("Last-Modified");
if ( last && last == nl ) {
// No change! Let's go.
f.log("Resource not modified: " + resource);
localStorage.setItem("ffz_age_" + resource, new Date().getTime());
f.getting[resource] = false;
return;
} else
// Save it!
localStorage.setItem("ffz_last_" + resource, nl);
}
} else if ( http.status === 304 ) {
// Not Modified!
f.log("Resource not modified: " + resource);
if ( window.localStorage )
localStorage.setItem("ffz_age_" + resource, new Date().getTime());
f.getting[resource] = false;
return;
} else if ( http.status === 404 ) {
// Not Found!
result = null;
} else {
// Try Again
if ( try_again() )
return;
result = undefined;
}
// Store it in localStorage if we can.
if ( window.localStorage && result !== undefined ) {
localStorage.setItem("ffz_" + resource, JSON.stringify(result));
localStorage.setItem("ffz_age_" + resource, new Date().getTime());
}
// And send it along.
f.getting[resource] = false;
try {
callback(result);
} catch(err) { f.log("Error in callback: " + err); }
}, false);
http.send();
}
// Finally, initialize FFZ.
window.ffz = new ffz();
})(this.unsafeWindow || window, window.chrome ? true : false); |