From 74e587b1b6d6349f50c082ed7afece8d43be546d Mon Sep 17 00:00:00 2001 From: Justin Karneges Date: Fri, 12 Apr 2013 02:50:21 -0700 Subject: rename to pollymer --- pollymer.js | 467 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 pollymer.js (limited to 'pollymer.js') diff --git a/pollymer.js b/pollymer.js new file mode 100644 index 0000000..d4cd078 --- /dev/null +++ b/pollymer.js @@ -0,0 +1,467 @@ +/** + * Pollymer JavaScript Library v1.0.0 + * Copyright 2013 Fan Out Networks, Inc. + * Released under the MIT license (see COPYING file in source distribution) + */ +(function () { +"use strict"; +var DEBUG = true; +(function (window, undefined) { + + var NAMESPACE = "Pollymer"; + var TIMEOUT = 60000; + var emptyMethod = function () { }; + + var consoleInfo; + var consoleError; + if (DEBUG) { + // don't break if there's no console + if (typeof (window.console) === "undefined") { + window.console = { info: emptyMethod, error: emptyMethod }; + } + consoleInfo = function (output) { window.console.info(output); }; + consoleError = function (output) { window.console.error(output); }; + } else { + consoleInfo = emptyMethod; + consoleError = emptyMethod; + } + + var copyArray = function (array) { + var args = Array.prototype.slice.call(arguments, 1); + return Array.prototype.slice.apply(array, args); + }; + + var indexOfItemInArray = function (array, item) { + for (var i = 0, length = array.length; i < length; i++) { + if (array[i] === item) { + return i; + } + } + return -1; + }; + var removeFromArray = function (array, item) { + var again = true; + while (again) { + var index = indexOfItemInArray(array, item); + if (index != -1) { + array.splice(index, 1); + } else { + again = false; + } + } + }; + + var parseResponseHeaders = function (headerStr) { + var headers = {}; + if (!headerStr) { + return headers; + } + var headerPairs = headerStr.split('\u000d\u000a'); + for (var i = 0; i < headerPairs.length; i++) { + var headerPair = headerPairs[i]; + var index = headerPair.indexOf('\u003a\u0020'); + if (index > 0) { + var key = headerPair.substring(0, index); + headers[key] = headerPair.substring(index + 2); + } + } + return headers; + }; + + var addJsonpScriptToDom = function (src, scriptId) { + var script = window.document.createElement("script"); + script.type = "text/javascript"; + script.id = scriptId; + script.src = src; + + var head = window.document.getElementsByTagName("head")[0]; + head.appendChild(script); + }; + var removeJsonpScriptFromDom = function (scriptId) { + var script = window.document.getElementById(scriptId); + script.parentNode.removeChild(script); + }; + + var jsonCallbacks = { + id: 0, + requests: {}, + getJsonpCallback: function (id) { + var cb; + var requests = this.requests; + if (id in this.requests) { + cb = function (result) { requests[id]._jsonpCallback(result); }; + } else { + consoleInfo("no callback with id " + id); + cb = emptyMethod; + } + return cb; + }, + addJsonpCallback: function (id, obj) { + this.requests[id] = obj; + }, + removeJsonpCallback: function (id) { + delete this.requests[id]; + }, + newCallbackInfo: function () { + var callbackInfo = { + id: "cb-" + this.id, + scriptId: "pd-jsonp-script-" + this.id + }; + this.id++; + return callbackInfo; + } + }; + + var corsAvailable = "withCredentials" in new window.XMLHttpRequest(); + + var sameOrigin = function (url) { + var loc = window.location; + var a = window.document.createElement('a'); + a.href = url; + return !a.hostname || (a.hostname == loc.hostname && a.port == loc.port && a.protocol == loc.protocol); + }; + + var chooseTransport = function (transportType, url) { + var transport; + if (transportType == transportTypes.Auto) { + if (corsAvailable || sameOrigin(url)) { + transport = transportTypes.Xhr; + } else { + transport = transportTypes.Jsonp; + } + } else { + transport = transportType; + } + return transport; + }; + + var Events = function () { + this._events = {}; + }; + Events.prototype._getHandlersForType = function (type) { + if (!(type in this._events)) { + this._events[type] = []; + } + return this._events[type]; + }; + Events.prototype.on = function (type, handler) { + var handlers = this._getHandlersForType(type); + handlers.push(handler); + }; + Events.prototype.off = function (type) { + if (arguments.length > 1) { + var handler = arguments[1]; + var handlers = this._getHandlersForType(type); + removeFromArray(handlers, handler); + } else { + delete this._events[type]; + } + }; + Events.prototype.trigger = function (type, obj) { + var args = copyArray(arguments, 2); + var handlers = copyArray(this._getHandlersForType(type)); + for (var i = 0, n = handlers.length; i < n; i++) { + var handler = handlers[i]; + handler.apply(obj, args); + } + }; + + var errorTypes = { + "TransportError": 0, + "TimeoutError": 1 + }; + + var transportTypes = { + "Auto": 0, + "Xhr": 1, + "Jsonp": 2 + }; + + // Pollymer.Request has callback members: + // on('finished', int code, object result, object headers) + // on('error', int reason) + + var Request = function () { + if (!(this instanceof Request)) { + throw new window.Error("Constructor called as a function"); + } + + this._events = new Events(); + this._tries = 0; + this._delayNext = false; + this._retryTime = 0; + this._timer = null; + this._jsonp = null; + + this._xhr = null; + this._method = null; + this._url = null; + this._headers = null; + this._body = null; + this._transport = null; + + this.transport = transportTypes.Auto; + this.rawResponse = false; + this.maxTries = 1; + this.maxDelay = 1000; + this.recurring = false; + + if (arguments.length > 0) { + var config = arguments[0]; + if ("transport" in config) { + this.transport = config.transport; + } + if ("rawResponse" in config) { + this.rawResponse = config.rawResponse; + } + if ("maxTries" in config) { + this.maxTries = config.maxTries; + } + if ("maxDelay" in config) { + this.maxDelay = config.maxDelay; + } + if ("recurring" in config) { + this.recurring = config.recurring; + } + } + }; + Request.prototype.start = function (method, url, headers, body) { + if (this._timer != null) { + consoleError("pollymer: start() called on a Request object that is currently running."); + return; + } + + this._method = method; + this._url = url; + this._headers = headers; + this._body = body; + this._start(); + }; + Request.prototype._start = function () { + this._tries = 0; + + var delayTime; + if (this._delayNext) { + this._delayNext = false; + delayTime = Math.floor(Math.random() * this.maxDelay); + consoleInfo("pollymer: polling again in " + delayTime + "ms"); + } else { + delayTime = 0; // always queue the call, to prevent browser "busy" + } + + this._initiate(delayTime); + }; + Request.prototype.retry = function () { + if (this._tries == 0) { + consoleError("pollymer: retry() called on a Request object that has never been started."); + return; + } + if (this._timer != null) { + consoleError("pollymer: retry() called on a Request object that is currently running."); + return; + } + this._retry(); + }; + Request.prototype._retry = function () { + if (this._tries === 1) { + this._retryTime = 1; + } else if (this._tries < 8) { + this._retryTime = this._retryTime * 2; + } + + var delayTime = this._retryTime * 1000; + delayTime += Math.floor(Math.random() * this.maxDelay); + consoleInfo("pollymer: trying again in " + delayTime + "ms"); + + this._initiate(delayTime); + }; + Request.prototype._initiate = function (delayMsecs) { + var self = this; + self._timer = window.setTimeout(function () { self._startConnect(); }, delayMsecs); + }; + Request.prototype._startConnect = function () { + var self = this; + this._timer = window.setTimeout(function () { self._timeout(); }, TIMEOUT); + + this._tries++; + + var method = this._method; + var url = (typeof (this._url) == "function") ? this._url() : this._url; + var headers = this._headers; + var body = this._body; + + // Create a copy of the transport because we don't want + // to give public access to it (changing it between now and + // cleanup would be a no-no) + this._transport = chooseTransport(this.transport, url); + + if (this._transport == transportTypes.Xhr) { + this._xhr = this._startXhr(method, url, headers, body); + } else { // Jsonp + this._jsonp = this._startJsonp(method, url, headers, body); + } + }; + Request.prototype._cleanupConnect = function (abort) { + window.clearTimeout(this._timer); + this._timer = null; + + if (this._transport == transportTypes.Xhr) { + consoleInfo("pollymer: XHR cleanup"); + this._cleanupXhr(this._xhr, abort); + this._xhr = null; + } else { // Jsonp + consoleInfo("pollymer: json-p " + this._jsonp.id + " cleanup"); + this._cleanupJsonp(this._jsonp, abort); + this._jsonp = null; + } + }; + Request.prototype.abort = function () { + this._cleanupConnect(true); + }; + Request.prototype.on = function (type, handler) { + this._events.on(type, handler); + }; + Request.prototype.off = function (type) { + var args = copyArray(arguments, 1); + args.unshift(type); + this._events.off.apply(this._events, args); + }; + Request.prototype._startXhr = function (method, url, headers, body) { + var xhr = new window.XMLHttpRequest(); + var self = this; + xhr.onreadystatechange = function () { self._xhrCallback(); }; + xhr.open(method, url, true); + + for (var key in headers) { + if (headers.hasOwnProperty(key)) { + xhr.setRequestHeader(key, headers[key]); + } + } + + xhr.send(body); + + consoleInfo("pollymer: XHR start " + url); + + return xhr; + }; + Request.prototype._cleanupXhr = function (xhr, abort) { + if (xhr != null) { + xhr.onreadystatechange = emptyMethod; + if (abort) { + xhr.abort(); + } + } + }; + Request.prototype._xhrCallback = function () { + var xhr = this._xhr; + if (xhr != null && xhr.readyState === 4) { + consoleInfo("pollymer: XHR finished"); + + var code = xhr.status; + var reason = xhr.statusText; + var headers = parseResponseHeaders(xhr.getAllResponseHeaders()); + var body = xhr.responseText; + + this._handleResponse(code, reason, headers, body); + } + }; + Request.prototype._startJsonp = function (method, url, headers, body) { + var jsonp = jsonCallbacks.newCallbackInfo(); + + var paramList = [ + "callback=" + encodeURIComponent("window['" + NAMESPACE + "']._getJsonpCallback(\"" + jsonp.id + "\")") + ]; + + if (method != "GET") { + paramList.push("_method=" + encodeURIComponent(method)); + } + if (headers) { + paramList.push("_headers=" + encodeURIComponent(JSON.stringify(headers))); + } + if (body) { + paramList.push("_body=" + encodeURIComponent(body)); + } + var params = paramList.join("&"); + + var src = (url.indexOf("?") != -1) ? url + "&" + params : url + "?" + params; + + jsonCallbacks.addJsonpCallback(jsonp.id, this); + addJsonpScriptToDom(src, jsonp.scriptId); + + consoleInfo("pollymer: json-p start " + jsonp.id + " " + src); + + return jsonp; + }; + Request.prototype._cleanupJsonp = function (jsonp, abort) { + if (jsonp != null) { + jsonCallbacks.removeJsonpCallback(jsonp.id); + removeJsonpScriptFromDom(jsonp.scriptId); + } + }; + Request.prototype._jsonpCallback = function (result) { + consoleInfo("pollymer: json-p " + this._jsonp.id + " finished"); + + var code = ("code" in result) ? result.code : 0; + var reason = ("reason" in result) ? result.reason : null; + var headers = ("headers" in result) ? result.headers : {}; + var body = ("body" in result) ? result.body : null; + + this._handleResponse(code, reason, headers, body); + }; + Request.prototype._handleResponse = function (code, reason, headers, body) { + this._cleanupConnect(); + + if ((code == 0 || (code >= 500 && code < 600)) && + (this.maxTries == -1 || this._tries < this.maxTries)) { + this._retry(); + } else { + if (code > 0) { + var result; + if (this.rawResponse) { + result = body; + } else { + try { + result = JSON.parse(body); + } catch (e) { + result = body; + } + } + this._finished(code, result, headers); + if (this.recurring && code >= 200 && code < 300) { + this._start(); + } + } else { + this._error(errorTypes.TransportError); + } + } + }; + Request.prototype._timeout = function () { + this._cleanupConnect(true); + + if (this.maxTries == -1 || this._tries < this.maxTries) { + this._retry(); + } else { + this._error(errorTypes.TimeoutError); + } + }; + Request.prototype._finished = function (code, result, headers) { + this._delayNext = true; + this._events.trigger('finished', this, code, result, headers); + }; + Request.prototype._error = function (reason) { + this._delayNext = true; + this._events.trigger('error', this, reason); + }; + + var exports = { + Request: Request, + TransportTypes: transportTypes, + ErrorTypes: errorTypes, + _getJsonpCallback: function (id) { + return jsonCallbacks.getJsonpCallback(id); + } + }; + window[NAMESPACE] = window[NAMESPACE] || exports; + +})(window); +})(); -- cgit v1.2.3