offline.js 7.83 KB
(function() {
  var Offline, checkXHR, defaultOptions, extendNative, grab, handlers, init;

  extendNative = function(to, from) {
    var e, key, results, val;
    results = [];
    for (key in from.prototype) {
      try {
        val = from.prototype[key];
        if ((to[key] == null) && typeof val !== 'function') {
          results.push(to[key] = val);
        } else {
          results.push(void 0);
        }
      } catch (_error) {
        e = _error;
      }
    }
    return results;
  };

  Offline = {};

  Offline.options = window.Offline ? window.Offline.options || {} : {};

  defaultOptions = {
    checks: {
      xhr: {
        url: function() {
          return "/favicon.ico?_=" + ((new Date()).getTime());
        },
        timeout: 5000,
        type: 'HEAD'
      },
      image: {
        url: function() {
          return "/favicon.ico?_=" + ((new Date()).getTime());
        }
      },
      active: 'xhr'
    },
    checkOnLoad: false,
    interceptRequests: true,
    reconnect: true,
    deDupBody: false
  };

  grab = function(obj, key) {
    var cur, i, j, len, part, parts;
    cur = obj;
    parts = key.split('.');
    for (i = j = 0, len = parts.length; j < len; i = ++j) {
      part = parts[i];
      cur = cur[part];
      if (typeof cur !== 'object') {
        break;
      }
    }
    if (i === parts.length - 1) {
      return cur;
    } else {
      return void 0;
    }
  };

  Offline.getOption = function(key) {
    var ref, val;
    val = (ref = grab(Offline.options, key)) != null ? ref : grab(defaultOptions, key);
    if (typeof val === 'function') {
      return val();
    } else {
      return val;
    }
  };

  if (typeof window.addEventListener === "function") {
    window.addEventListener('online', function() {
      return setTimeout(Offline.confirmUp, 100);
    }, false);
  }

  if (typeof window.addEventListener === "function") {
    window.addEventListener('offline', function() {
      return Offline.confirmDown();
    }, false);
  }

  Offline.state = 'up';

  Offline.markUp = function() {
    Offline.trigger('confirmed-up');
    if (Offline.state === 'up') {
      return;
    }
    Offline.state = 'up';
    return Offline.trigger('up');
  };

  Offline.markDown = function() {
    Offline.trigger('confirmed-down');
    if (Offline.state === 'down') {
      return;
    }
    Offline.state = 'down';
    return Offline.trigger('down');
  };

  handlers = {};

  Offline.on = function(event, handler, ctx) {
    var e, events, j, len, results;
    events = event.split(' ');
    if (events.length > 1) {
      results = [];
      for (j = 0, len = events.length; j < len; j++) {
        e = events[j];
        results.push(Offline.on(e, handler, ctx));
      }
      return results;
    } else {
      if (handlers[event] == null) {
        handlers[event] = [];
      }
      return handlers[event].push([ctx, handler]);
    }
  };

  Offline.off = function(event, handler) {
    var _handler, ctx, i, ref, results;
    if (handlers[event] == null) {
      return;
    }
    if (!handler) {
      return handlers[event] = [];
    } else {
      i = 0;
      results = [];
      while (i < handlers[event].length) {
        ref = handlers[event][i], ctx = ref[0], _handler = ref[1];
        if (_handler === handler) {
          results.push(handlers[event].splice(i, 1));
        } else {
          results.push(i++);
        }
      }
      return results;
    }
  };

  Offline.trigger = function(event) {
    var ctx, handler, j, len, ref, ref1, results;
    if (handlers[event] != null) {
      ref = handlers[event].slice(0);
      results = [];
      for (j = 0, len = ref.length; j < len; j++) {
        ref1 = ref[j], ctx = ref1[0], handler = ref1[1];
        results.push(handler.call(ctx));
      }
      return results;
    }
  };

  checkXHR = function(xhr, onUp, onDown) {
    var _onerror, _onload, _onreadystatechange, _ontimeout, checkStatus;
    checkStatus = function() {
      if (xhr.status && xhr.status < 12000) {
        return onUp();
      } else {
        return onDown();
      }
    };
    if (xhr.onprogress === null) {
      _onerror = xhr.onerror;
      xhr.onerror = function() {
        onDown();
        return typeof _onerror === "function" ? _onerror.apply(null, arguments) : void 0;
      };
      _ontimeout = xhr.ontimeout;
      xhr.ontimeout = function() {
        onDown();
        return typeof _ontimeout === "function" ? _ontimeout.apply(null, arguments) : void 0;
      };
      _onload = xhr.onload;
      return xhr.onload = function() {
        checkStatus();
        return typeof _onload === "function" ? _onload.apply(null, arguments) : void 0;
      };
    } else {
      _onreadystatechange = xhr.onreadystatechange;
      return xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
          checkStatus();
        } else if (xhr.readyState === 0) {
          onDown();
        }
        return typeof _onreadystatechange === "function" ? _onreadystatechange.apply(null, arguments) : void 0;
      };
    }
  };

  Offline.checks = {};

  Offline.checks.xhr = function() {
    var e, xhr;
    xhr = new XMLHttpRequest;
    xhr.offline = false;
    xhr.open(Offline.getOption('checks.xhr.type'), Offline.getOption('checks.xhr.url'), true);
    if (xhr.timeout != null) {
      xhr.timeout = Offline.getOption('checks.xhr.timeout');
    }
    checkXHR(xhr, Offline.markUp, Offline.markDown);
    try {
      xhr.send();
    } catch (_error) {
      e = _error;
      Offline.markDown();
    }
    return xhr;
  };

  Offline.checks.image = function() {
    var img;
    img = document.createElement('img');
    img.onerror = Offline.markDown;
    img.onload = Offline.markUp;
    img.src = Offline.getOption('checks.image.url');
    return void 0;
  };

  Offline.checks.down = Offline.markDown;

  Offline.checks.up = Offline.markUp;

  Offline.check = function() {
    Offline.trigger('checking');
    return Offline.checks[Offline.getOption('checks.active')]();
  };

  Offline.confirmUp = Offline.confirmDown = Offline.check;

  Offline.onXHR = function(cb) {
    var _XDomainRequest, _XMLHttpRequest, monitorXHR;
    monitorXHR = function(req, flags) {
      var _open;
      _open = req.open;
      return req.open = function(type, url, async, user, password) {
        cb({
          type: type,
          url: url,
          async: async,
          flags: flags,
          user: user,
          password: password,
          xhr: req
        });
        return _open.apply(req, arguments);
      };
    };
    _XMLHttpRequest = window.XMLHttpRequest;
    window.XMLHttpRequest = function(flags) {
      var _overrideMimeType, _setRequestHeader, req;
      req = new _XMLHttpRequest(flags);
      monitorXHR(req, flags);
      _setRequestHeader = req.setRequestHeader;
      req.headers = {};
      req.setRequestHeader = function(name, value) {
        req.headers[name] = value;
        return _setRequestHeader.call(req, name, value);
      };
      _overrideMimeType = req.overrideMimeType;
      req.overrideMimeType = function(type) {
        req.mimeType = type;
        return _overrideMimeType.call(req, type);
      };
      return req;
    };
    extendNative(window.XMLHttpRequest, _XMLHttpRequest);
    if (window.XDomainRequest != null) {
      _XDomainRequest = window.XDomainRequest;
      window.XDomainRequest = function() {
        var req;
        req = new _XDomainRequest;
        monitorXHR(req);
        return req;
      };
      return extendNative(window.XDomainRequest, _XDomainRequest);
    }
  };

  init = function() {
    if (Offline.getOption('interceptRequests')) {
      Offline.onXHR(function(arg) {
        var xhr;
        xhr = arg.xhr;
        if (xhr.offline !== false) {
          return checkXHR(xhr, Offline.markUp, Offline.confirmDown);
        }
      });
    }
    if (Offline.getOption('checkOnLoad')) {
      return Offline.check();
    }
  };

  setTimeout(init, 0);

  window.Offline = Offline;

}).call(this);