(function(){
var l,
    doAuth,
    h = [],
    valid = false,
    a = "",
    fwk = "https://platform.linkedin.com/js/framework?v=1.0.319-1429",
    xtnreg = /extensions=([^&]*)&?/,
    xtn = fwk.match(xtnreg),
    dotRegex = /\./g,
    starRegex = /\*/g,
    selfUrl = window.location.host.replace(/:\d+$/,"").toLowerCase();
window.IN = window.IN || {};

// if cws support is being handled by XDoor...
if (window.IN && window.IN.cws && window.IN.cws.poweredByXDoor) {
  // abort
  return;
}

IN.ENV = IN.ENV || {};
IN.ENV.js = IN.ENV.js || {};
IN.ENV.js.xtn = IN.ENV.js.xtn || {};
/*
if !ANONYMOUS_USERSPACE
endif !ANONYMOUS_USERSPACE
*/
/*
# --------------------
# ----- FRAGMENT -----
# --------------------
*/
/*
if AUTH_USERSPACE
  else if FULL_USERSPACE || ANONYMOUS_USERSPACE
  */
    l = IN.ENV.ui = IN.ENV.ui || {};
    l.popup_window_width = 600;
    l.window_width = 100;
    l = IN.ENV.auth = IN.ENV.auth || {};
  /*
  if !LIX_DISABLE_USERSPACE_OAUTH
    */
    l.oauth_token = "";
    l.oauth_expires_in = parseInt("0", 10);
  /*
    endif !LIX_DISABLE_USERSPACE_OAUTH
  */
    l.anonymous_token = "";
    l.anonymous_expires_in = parseInt("${ANONYMOUS_EXPIRES_IN}", 10);
    l.member_id = "";
    l.api_key = "";
    l = IN.ENV.api = IN.ENV.api || {};
    l.queue_interval = parseInt("300", 10);
    l = IN.ENV.url =  IN.ENV.url || {};
    l.xd_html = "https://platform.linkedin.com/js/xdrpc.html?v=1.0.319-1429";
    l.xd_us_html = "https://platform.linkedin.com/js/xdrpc.html?v=1.0.319-1429";
    l.api_xd_html = "https://api.linkedin.com/uas/js/xdrpc.html?v=1.0.319-1429";
    l.api = "https://api.linkedin.com/v1";
    l.login = "https://www.linkedin.com/uas/connect/user-signin";
    l.authorize = "https://www.linkedin.com/uas/oauth2/authorize?immediate=true";
    l.silent_auth_url = "${SILENT_AUTHORIZE_URL}";
    l.logout = "https://www.linkedin.com/uas/connect/logout?oauth_token={OAUTH_TOKEN}&api_key={API_KEY}&callback={CALLBACK}";
    l.userspace_renew = "https://www.linkedin.com/uas/js/authuserspace?v=1.0.319-1429&api_key={API_KEY}";
    l.base_js_url = "${DEFAULT_JS_URL}";
    l.analytics_us_url = "https://www.linkedin.com/analytics/?type=__ETYPE__&trackingInfo=__TRKINFO__&trk=__TINFO__&or=__ORIGIN__&wt=__WTYPE__";
    l.analytics_url = "https://www.linkedin.com/analytics/?type=__ETYPE__&trackingInfo=__TRKINFO__&trk=__TINFO__&or=__ORIGIN__&wt=__WTYPE__";

    l = IN.ENV.widget = IN.ENV.widget || {};
    l.alumni_url = "https://www.linkedin.com/cws/alumni";
    l.followmember_url = "https://www.linkedin.com/cws/followmember";
    l.settings_url = "https://www.linkedin.com/cws/settings";
    l.share_url = "https://www.linkedin.com/cws/share";
    l.share_counter_url = "https://www.linkedin.com/countserv/count/share";
    l.recommend_product_url = "https://www.linkedin.com/company/{COMPANY_ID}/product?prdId={PRODUCT_ID}";
    l.recommend_product_counter_url = "https://www.linkedin.com/company/api/recommendation/count?type=PDCT&id={PRODUCT_ID}&callback={CALLBACK}";
    l.company_url = "https://www.linkedin.com/cws/company/profile";
    l.member_profile_url = "https://www.linkedin.com/cws/member/public_profile";
    l.full_member_profile_url = "https://www.linkedin.com/cws/member/full_profile";
    l.referral_center_url= "https://www.linkedin.com/cws/referral";
    l.apply_url= "${WIDGET_APPLY_URL}";
    l.mail_url= "https://www.linkedin.com/cws/mail";
    l.apply_counter_url = "${WIDGET_APPLY_COUNTER_URL}";
    l.company_insider_url = "https://www.linkedin.com/cws/company/insider";
    l.sfdc_member_url = "https://www.linkedin.com/cws/sfdc/member";
    l.sfdc_company_url = "https://www.linkedin.com/cws/sfdc/company";
    l.sfdc_signal_url = "https://www.linkedin.com/cws/sfdc/signal";
    l.cap_recruiter_member_url = "${WIDGET_CAP_RECRUITER_MEMBER_URL}";
    l.jymbii_url = "https://www.linkedin.com/cws/jymbii";
    l.today_url = "https://www.linkedin.com/cws/today/today";
    l.followcompany_url = "https://www.linkedin.com/cws/followcompany";
    l.login_url = "https://www.linkedin.com/cws/login";
    l.alumni_facet_url = "https://www.linkedin.com/college/alumni-facet-extension";
    l.csap_beacon_url = "https://www.linkedin.com/cws/csap/beacon";
  /*
  if LIX_DISABLE_WIDGET_APPLY
  */
    l.disable_widget_apply = false;
    /*
  endif LIX_DISABLE_WIDGET_APPLY
  */

    l = IN.ENV.images = IN.ENV.images || {};
    l.sprite = "http://s.c.lnkd.licdn.com/scds/common/u/images/apps/connect/sprites/sprite_connect_v14.png";
    l.unsecure_xdswf = "https://platform.linkedin.com/js/easyXDM.swf?v=1.0.319-1429";
    l.secure_xdswf = "https://platform.linkedin.com/js/easyXDM.swf?v=1.0.319-1429";


    /*
     # Client Side Extensions
     # These are possibly in framework js and need to be loaded
     # via IN.$extensions() instead. This also helps ensure we're under
     # the 2048 limit for URL length in cases where a lot of extensions
     # are being loaded
     */
    if (xtn && xtn[1] && IN.$extensions) {
      IN.$extensions(decodeURIComponent(xtn[1]));
      fwk = fwk.replace(xtn[0], "").replace(/&*$/, "");
    }
/*
endif
*/
/*
if FULL_USERSPACE
endif
*/
/*
# --------------------
# ----- FRAGMENT -----
# --------------------
*/
/*
if !ANONYMOUS_USERSPACE
endif !ANONYMOUS_USERSPACE
*/
})();

/**
 * Connect 2.0.0
 * The Embeddable LinkedIn Experience
 *
 * This is the Connect JS framework file that is fetched using the
 * in.js bootstrap and setting the v2 property to true:
 *
 * <script src="http://platform.linkedin.com/in.js">
 *   api_key: some-key-here
 *   v2: true
 * </script>
 *
 * Connect 2 (codename "Reconnect") offers many improvements over it's
 * predecessor, notably full compatibility on iOS and speed improvements.
 *
 * For more information:
 * go/reconnect
 */
(function(win, undef) {

  var doc = win.document;

  /* Lib */
  //     Fiber.js 1.0.5
  //     @author: Kirollos Risk
  //
  //     Copyright (c) 2012 LinkedIn.
  //     All Rights Reserved. Apache Software License 2.0
  //     http://www.apache.org/licenses/LICENSE-2.0

  (function () {
    /*jshint bitwise: true, camelcase: false, curly: true, eqeqeq: true,
      forin: false, immed: true, indent: 2, latedef: true, newcap: false,
      noarg: true, noempty: false, nonew: true, plusplus: false,
      quotmark: single, regexp: false, undef: true, unused: true, strict: false,
      trailing: true, asi: false, boss: false, debug: false, eqnull: true,
      es5: false, esnext: false, evil: true, expr: false, funcscope: false,
      iterator: false, lastsemic: false, laxbreak: false, laxcomma: false,
      loopfunc: false, multistr: true, onecase: false, proto: false,
      regexdash: false, scripturl: false, smarttabs: false, shadow: true,
      sub: true, supernew: true, validthis: false */

    /*global exports, global, define, module */

    (function (root, factory) {
      if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(this);
      } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(function () {
          return factory(root);
        });
      } else {
        // Browser globals (root is window)
        root.Fiber = factory(root);
      }
    }(this, function (global) {

      // Baseline setup
      // --------------

      // Stores whether the object is being initialized. i.e., whether
      // to run the `init` function, or not.
      var initializing = false,

      // Keep a few prototype references around - for speed access,
      // and saving bytes in the minified version.
      ArrayProto = Array.prototype,

      // Save the previous value of `Fiber`.
      previousFiber = global.Fiber;

      // Helper function to copy properties from one object to the other.
      function copy(from, to) {
        var name;
        for (name in from) {
          if (from.hasOwnProperty(name)) {
            to[name] = from[name];
          }
        }
      }

      // The base `Fiber` implementation.
      function Fiber() {}

      // ###Extend
      //
      // Returns a subclass.
      Fiber.extend = function (fn) {
        // Keep a reference to the current prototye.
        var parent = this.prototype,

        // Invoke the function which will return an object literal used to
        // define the prototype. Additionally, pass in the parent prototype,
        // which will allow instances to use it.
        properties = fn(parent),

        // Stores the constructor's prototype.
        proto;

        // The constructor function for a subclass.
        function child() {
          if (!initializing) {
            // Custom initialization is done in the `init` method.
            this.init.apply(this, arguments);
            // Prevent subsequent calls to `init`. Note: although a `delete
            // this.init` would remove the `init` function from the instance, it
            // would still exist in its super class' prototype.  Therefore,
            // explicitly set `init` to `void 0` to obtain the `undefined`
            // primitive value (in case the global's `undefined` property has
            // been re-assigned).
            this.init = void 0;
          }
        }

        // Instantiate a base class (but only create the instance, without
        // running `init`). And, make every `constructor` instance an instance
        // of `this` and of `constructor`.
        initializing = true;
        proto = child.prototype = new this;
        initializing = false;

        // Add default `init` function, which a class may override; it should
        // call the super class' `init` function (if it exists);
        proto.init = function () {
          if (typeof parent.init === 'function') {
            parent.init.apply(this, arguments);
          }
        };

         // Copy the properties over onto the new prototype.
        copy(properties, proto);

        // Enforce the constructor to be what we expect.
        proto.constructor = child;

        // Keep a reference to the parent prototype.
        // (Note: currently used by decorators and mixins, so that the parent
        // can be inferred).
        child.__base__ = parent;

        // Make this class extendable, this can be overridden by providing a
        // custom extend method on the proto.
        child.extend = child.prototype.extend || Fiber.extend;


        return child;
      };

      // Utilities
      // ---------

      // ###Proxy
      //
      // Returns a proxy object for accessing base methods with a given context.
      //
      // - `base`: the instance' parent class prototype.
      // - `instance`: a Fiber class instance.
      //
      // Overloads:
      //
      // - `Fiber.proxy( instance )`
      // - `Fiber.proxy( base, instance )`
      //
      Fiber.proxy = function (base, instance) {
        var name,
          iface = {},
          wrap;

        // If there's only 1 argument specified, then it is the instance,
        // thus infer `base` from its constructor.
        if (arguments.length === 1) {
          instance = base;
          base = instance.constructor.__base__;
        }

        // Returns a function which calls another function with `instance` as
        // the context.
        wrap = function (fn) {
          return function () {
            return base[fn].apply(instance, arguments);
          };
        };

        // For each function in `base`, create a wrapped version.
        for (name in base) {
          if (base.hasOwnProperty(name) && typeof base[name] === 'function') {
            iface[name] = wrap(name);
          }
        }
        return iface;
      };

      // ###Decorate
      //
      // Decorate an instance with given decorator(s).
      //
      // - `instance`: a Fiber class instance.
      // - `decorator[s]`: the argument list of decorator functions.
      //
      // Note: when a decorator is executed, the argument passed in is the super
      // class' prototype, and the context (i.e. the `this` binding) is the
      // instance.
      //
      //  *Example usage:*
      //
      //     function Decorator( base ) {
      //       // this === obj
      //       return {
      //         greet: function() {
      //           console.log('hi!');
      //         }
      //       };
      //     }
      //
      //     var obj = new Bar(); // Some instance of a Fiber class
      //     Fiber.decorate(obj, Decorator);
      //     obj.greet(); // hi!
      //
      Fiber.decorate = function (instance /*, decorator[s] */) {
        var i,
          // Get the base prototype.
          base = instance.constructor.__base__,
          // Get all the decorators in the arguments.
          decorators = ArrayProto.slice.call(arguments, 1),
          len = decorators.length;

        for (i = 0; i < len; i++) {
          copy(decorators[i].call(instance, base), instance);
        }
      };

      // ###Mixin
      //
      // Add functionality to a Fiber definition
      //
      // - `definition`: a Fiber class definition.
      // - `mixin[s]`: the argument list of mixins.
      //
      // Note: when a mixing is executed, the argument passed in is the super
      // class' prototype (i.e., the base)
      //
      // Overloads:
      //
      // - `Fiber.mixin( definition, mix_1 )`
      // - `Fiber.mixin( definition, mix_1, ..., mix_n )`
      //
      // *Example usage:*
      //
      //     var Definition = Fiber.extend(function(base) {
      //       return {
      //         method1: function(){}
      //       }
      //     });
      //
      //     function Mixin(base) {
      //       return {
      //         method2: function(){}
      //       }
      //     }
      //
      //     Fiber.mixin(Definition, Mixin);
      //     var obj = new Definition();
      //     obj.method2();
      //
      Fiber.mixin = function (definition /*, mixin[s] */) {
        var i,
          // Get the base prototype.
          base = definition.__base__,
          // Get all the mixins in the arguments.
          mixins = ArrayProto.slice.call(arguments, 1),
          len = mixins.length;

        for (i = 0; i < len; i++) {
          copy(mixins[i](base), definition.prototype);
        }
      };

      // ###noConflict
      //
      // Run Fiber.js in *noConflict* mode, returning the `fiber` variable to
      // its previous owner. Returns a reference to the Fiber object.
      Fiber.noConflict = function () {
        global.Fiber = previousFiber;
        return Fiber;
      };

      return Fiber;
    }));
  } ());
  /*
  Espany
  (c) 2014 LinkedIn Corp.  All rights reserved.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an "AS
  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  express or implied. See the License for the specific language
  governing permissions and limitations under the License.
  */

  (function(win) {

    // JSON-RPC version required for all verison 2.0 messages.
    var JSON_RPC_VERSION = '2.0',

      // The 'message' event string.
      MESSAGE_EVENT = 'message',

      // An internal method that reports all available methods that may be called remotely.
      DISCOVER_EXTENSION = 'rpc.discover',

      // An origin of the consumer, passed to the provider via URL parameter.
      ORIGIN_PARAM = 'e_origin',

      // A unique channel id set in the consumer, passed to the provider via URL parameter.
      CHANNEL_PARAM = 'e_channel',

      manager,

      /**
       * This singleton is a collection of utility methods provided to
       * make life easier in general. They are available to all components.
       */
      Util = {

        /**
         * A utility method to bind a function to a context.
         * @param  {Function} func    The function to bind.
         * @param  {Object} context   The context to bind it to.
         * @return {Function}         The bound function.
         */
        bind: function(func, context) {
          return function() {
            func.apply(context, arguments);
          };
        },

        /**
         * Reports whether or not the passed object is an array.
         * @param  {*}  obj The object you whish to inspect.
         * @return {Boolean}     Is the object an array?
         */
        isArray: function(obj) {
          return Object.prototype.toString.call(obj) === '[object Array]';
        },

        /**
         * A helper function to grab the window's query string and convert it into a friendly object.
         * @return {Object} The window's query string in key-value format.
         */
        getQueryParams: function() {

          var pairs = win.location.search.slice(1).split('&'),
              params = {},
              param,
              pair;

          function xssSanitize(text) {
            return text.replace(/[\x00'"<\\]/g, '\uFFFD');
          }

          while (pairs.length) {
            pair = pairs.pop();
            if (pair) {
              param = pair.split('=');
              if (param.length > 1) {
                params[param[0]] = xssSanitize(param[1]);
              }
            }
          }

          return params;
        }

      };

    // Configure event listener methods based on browser capabilities.
    // Standard support.
    if (win.addEventListener) {
      Util.addListener = function(el, evt, fn) {
          el.addEventListener(evt, fn, false);
      };
    }

    // Old IE support.
    else if (win.attachEvent) {
      Util.addListener = function(el, evt, fn) {
        el.attachEvent('on' + evt, fn);
      };
    }


    /**
     * The Manager is responsible for keeping track of communication nodes
     * and requests on a single document, as well as listening for and
     * processing messages.
     * @constructor
     */
    function Manager() {

      // A map of all the communication nodes.
      this.nodes = {};

      // A map of requests currently being processed.
      this.requests = {};

      // Begin listening for message events.
      Util.addListener(win, MESSAGE_EVENT, Util.bind(this.receiveMessage, this));
    }

    Manager.prototype = {

      /**
       * Creates a new communication node with provided options
       * and adds it to the nodes map.
       * @param {Object} options A map of node options.
       * @return {Node} A node communication object.
       */
      addNode: function(options) {
        var node = new Node(options);
        this.nodes[node.channel] = node;
        return node;
      },

      /**
       * Creates a new procedure request and adds it to the requests
       * map, providing access to it's callbacks to be invoked later.
       * @param {String} method The remote method to invoke.
       * @param {*} params Arguments to invoke the method with.
       * @return {Request} A request object.
       */
      addRequest: function(method, params) {
        var request = new Request(method, params);
        this.requests[request.id] = request;
        return request;
      },

      /**
       * Removes the given node from the nodes map and destroys it.
       * @param  {Node} node The node object to remove.
       */
      removeNode: function(node) {
        delete this.nodes[node.channel];
        node.destroy();
      },

      /**
       * This method is fired on the 'message' event. It will properly route the data
       * based on it's contents (request, result, or error).
       * @param  {Object} evt A 'message' event.
       */
      receiveMessage: function(evt) {

        var data = JSON.parse(evt.data),
            node = this.nodes[data.channel];

        console.log('[Message Received @ ' + node.channel + ']', evt, data);

        // Process the message only if it's from the same channel and remote origin.
        if (node && evt.origin === node.remoteOrigin) {

          // Process the request method!
          if (data.method) {
            this.processMessage(node, data);
          }

          // Else, it must be result data. Process the request and remove it.
          else {
            this.requests[data.id].process(data);
            delete this.requests[data.id];
          }
        }
      },

      /**
       * Calls the provided request method and configures callbacks used to respond.
       * @param  {Object} data The request object that was created using _buildRequestPayload.
       */
      processMessage: function(node, data) {

        console.log('---> Request:', data);

        // Grab a reference to the method requested.
        var method = node.methods[data.method],

            // Build callbacks to potentially be used within the method.
            success = node.buildCallback(this._buildResultPayload, data.id),
            error = node.buildCallback(this._buildErrorPayload, data.id),

            // The arguments to pass to the method.
            params = data.params,

            // The method's returned result.
            result;

        // Convert params to an array so we can add callbacks and apply.
        params = Util.isArray(params) ? params : [params];
        result = method.apply(node, params.concat(success, error));

        // If there's an immediate result, send the response.
        // Otherwise, a response may be generated via callback or not applicable
        // (the case of notifications).
        if (result) {
          success(result);
        }
      },

      /**
       * Creates a response message to be sent to the consumer.
       * @param  {*} result The calculated result data.
       * @param  {Number} id     The original request's id.
       * @return {Object}        The built payload.
       */
      _buildResultPayload: function(result, id) {
        return {
          result: result,
          id: id
        };
      },

      /**
       * Creates an error message to be sent to the consumer.
       * @param  {String} error The error description.
       * @param  {Number} id    The original request's id.
       * @return {Object}       The built payload.
       */
      _buildErrorPayload: function(error, id) {
        return {
          error: {
            // Internal server error code from the spec.
            code: -32099,
            message: error
          },
          id: id
        };
      }

    };


    /**
     * The core communication object used to determine it's remote counterpart
     * and send messages. Nodes are configured via proxy objects so they are not
     * exposed to the user.
     * @constructor
     * @param {Object} options
     *        A map of configuration options.
     * @param {String} options.remote
     *        Path to a remote document to load. Used only in parent documents.
     * @param {String|Number} options.channel
     *        A unique identifier for a pair of communication nodes. If this is
     *        not provided, a randomized identifier will be generated.
     * @param {Function} options.ready
     *        A function to invoke when the node has fully initialized.
     *        Only necessary on consumer nodes as they must wait for providers
     *        to load before remotely invoking their methods.
     * @param {Object} options.methods
     *        A map of methods available to be invoked remotely, keyed by name.
     *        These methods may return values or employ the use of 'success' and
     *        'error' callback methods that can be optionally passed in.
     * @param {Object} options.container
     *        A map of container element attributes to be applied, keyed by name.
     *        The most common usage is to style the container via 'style' and
     *        'className', however any valid DOM attributes (id, name, etc.) could
     *        be used. Note that including a 'container' option will automatically
     *        override all the element defaults, make the communication container
     *        visible. In this case, append it to the DOM using Proxy#container().
     *        Used only in parent documents.
     */
    function Node(options) {

      var queryParams = Util.getQueryParams(),
          remote;

      // If no options are passed, default to an empty object.
      // But, seriously, none of this will work without options.
      options = options || {};

      // Cache the remote option as it is referenced multiple times.
      remote = options.remote;

      // Set a unique channel id to help identify the communication components.
      // If a specific channel is not passed, try locating it in the query string.
      // As a last resort, generate one using the current timestamp.
      this.channel = options.channel || queryParams[CHANNEL_PARAM] || 'e_' + new Date().getTime();

      // If this is the consumer, the container property contains a reference to
      // the provider iframed document.
      this.container = remote ? new Container(this.channel, remote, options.container) : null;

      // This will be either an iframe's contentWindow (if this is the consumer)
      // or the parent window (if this is the provider), which is set immediately.
      // The communication iframe's window may not be available yet (for instance,
      // if options were passed to manually place the container) so an attempt
      // to set this will happen in _buildReady.
      if (!remote) {
        this.remoteWindow = win.parent;
      }

      // A communication remote (a URL to the provider) is either supplied by
      // the options object (set in the consumer) or in the URL parameters
      // (to be set in the provider).
      this.remoteOrigin = this._getRemoteOrigin(remote) || queryParams[ORIGIN_PARAM];

      // Set methods, defaulting to an empty object.
      this.methods = options.methods || {};

      // Set a special discovery method that returns available methods to call.
      this.methods[DISCOVER_EXTENSION] = this._buildReady(options.ready);

      // If this is the provider (checked by the existence of a container object
      // as providers do not have a container), send the discovery notification.
      // If this is the parent (consumer), we'll wait until the child is ready before sending the discovery.
      // This is sent even if the provider doesn't have any methods (in this case it is
      // technically a consumer), as it will notify the other node of it's ready state.
      if (!this.container) {
        this._sendDiscovery();
      }
    }

    Node.prototype = {

      /**
       * Sends a message to the remote Espany component using postMessage.
       * Appends the JSON-RPC version string to the message to satisfy the spec.
       * @param  {Object} payload A message formatted using one of the 'payload' methods.
       */
      send: function(payload) {

        var self = this;

        // Set the JSON-RPC property per the 2.0 spec.
        payload.jsonrpc = JSON_RPC_VERSION;

        // Set the channel so the proper Espany node received the message.
        // Note that 'channel' is not in the spec but I don't see anything discouraging
        // the use of custom properties.
        payload.channel = self.channel;

        // Post the payload to the remote window, expecting the set remote origin.
        // postMessage is synchronous in IE8 so we can force async using setTimeout.
        win.setTimeout(function() {
          self.remoteWindow.postMessage(JSON.stringify(payload), self.remoteOrigin);
        }, 10);
      },

      /**
       * Removes the communication container (provider) from the document and stops
       * listening for the 'message' event.
       */
      destroy: function() {
        if (this.container) {
          this.container.destroy();
          this.container = null;
        }
      },

      /**
       * Creates a callback for use in remote methods, sending data back to the consumer.
       * @param  {Function} method The payload-generating method to invoke.
       * @param  {String} id     The request id related to the callback.
       * @return {Function}      A method generated to send the payload.
       */
      buildCallback: function(method, id) {
        var self = this;
        return function() {
          self.send(method(arguments[0], id));
        };
      },

      /**
       * Creates a method will be called when the discovery method is invoked.
       * If this is the parent and the remoteWindow hasn't been set yet,
       * the callback invocation will be delayed until remoteWindow is set.
       * @param  {Function} callback The method to be called on ready.
       */
      _buildReady: function(callback) {

        var self = this;

        return function() {

          var container = self.container,
              el = container && container.el;

          // If this is the parent (a container exists) and remoteWindow
          // has not been set, try a few differet ways to set it.
          if (!self.remoteWindow && container) {
            console.log('remoteWindow not set. Attempting to set.');
            self.remoteWindow = el.contentWindow;

            // If it's still not set, the container's iframe must still be loading.
            // Listen for onload and then attempt to set remoteWindow again.
            if (!self.remoteWindow) {
              console.log('remoteWindow _still_ not set. Listening for container load.');
              Util.addListener(el, 'load', function() {
                self.remoteWindow = el.contentWindow;
                callback.apply(self, arguments);
              });
              return;
            }
          }

          // Otherwise, just invoke the callback.
          callback.apply(self, arguments);
        };
      },

      /**
       * Send the internal 'rpc.discovery' method, informing the remote component of the
       * available methods that can be called.
       */
      _sendDiscovery: function() {
        var request = new Request(DISCOVER_EXTENSION, [this._getMethodNames()], true);
        this.send(request.payload);
      },

      /**
       * Collects all the available methods that can be called remotely.
       * @return {Array} A list of available methods.
       */
      _getMethodNames: function() {

        var methods = this.methods,
            methodNames = [],
            method;

        for (method in methods) {
          methodNames.push(method);
        }

        return methodNames;
      },

      /**
       * Finds the remote origin:
       * For the consumer, this should be passed in via options.remote.
       * For the provider, this will be automatically included in the URL.
       * @param  {String} url The remote document remote URL.
       * @return {String}     The origin.
       */
      _getRemoteOrigin: function(url) {

        var segments;

        // If a URL string was passed, parse the origin and return it.
        if (url && typeof url === 'string') {
          segments = url.split('/');
          return segments[0] + '//' + segments[2];
        }
        return '';
      }

    };


    /**
     * A communication iframe container pointing to a remote document (the provider).
     * If attributes are passed, they will be applied to the iframe and, in this case, the iframe
     * will need to be manually placed on the page using a reference stored in proxy.container().
     * Otherwise, the iframe will be hidden from view.
     * @constructor
     * @param  {String} id A unique identifier for the container.
     * @param  {String} url The remote document URL.
     * @param  {Object} [attributes] Attributes that should be added to the container.
     */
    function Container(id, url, attributes) {

      // Cached reference to the document object.
      var doc = win.document,

          // Create an iframe and reference it in an attribute.
          el = this.el = doc.createElement('iframe');

      // Set the iframe's default id and name. These can be overridden via attributes.
      el.name = el.id = id;

      // Build the iframe's source.
      el.src = this.buildFrameSrc(url);

      // If attributes are passed, attach them to the container.
      // Note that the container is now in manual mode; you'll need to append it yourself.
      if (attributes) {
        this.setAttributes(attributes);
        console.log('[Manual Container Mode] \'container\' option was set. Remember to append to the DOM using .container()');
      }

      // Otherwise, set the container defaults to hide it and then append it to the body.
      else {
        this.setDefaults();
        doc.getElementsByTagName('body')[0].appendChild(el);
      }

    }

    Container.prototype = {

      /**
       * Removes the communication iframe from the document and
       * sets the reference to null.
       */
      destroy: function() {
        var el = this.el;
        if (el && el.parentNode) {
          el.parentNode.removeChild(el);
        }
        this.el = null;
      },

      /**
       * Loops through the passed attributes map, setting each one on the
       * communication iframe. These are commonly styling attributes such as
       * 'style' and 'className', although any attributes commonly found on
       * DOM elements are valid.
       * @param {Object} attributes A map of attributes and their values to set.
       */
      setAttributes: function(attributes) {
        var el = this.el,
            attribute;
        for (attribute in attributes) {
          if (attribute === 'style') {
            el.style.cssText = attributes[attribute];
          }
          else {
            el[attribute] = attributes[attribute];
          }
        }
      },

      /**
       * Sets default styling to the communication iframe, hiding it from view.
       */
      setDefaults: function() {
        var style = this.el.style;
        style.visibility = 'hidden';
        style.width = '1px';
        style.height = '1px';
        style.position = 'absolute';
        style.left = '-999px';
        style.top = '0';
      },

      /**
       * The remote document loaded in the iframe needs a reference to the consumer
       * (document that opened the iframe) to enable communication, passed via a
       * URL parameter. Here we properly append the parameter, taking care not to
       * mangle any other queries.
       * @param  {String} remote URL to the provider/server.
       * @return {String}        The URL extended with our parameter.
       */
      buildFrameSrc: function(url) {

        // Determine this document's origin to pass to the provider/server.
        // Break apart the remote URL to properly insert our communication param.
        var loc = win.location,
            origin = loc.origin ? loc.origin : loc.protocol + '//' + loc.host,
            originParam = ORIGIN_PARAM + '=' + origin,
            hashParts = url.split('#'),
            queryParts = hashParts[0].split('?');

        // If the remote URL has a query, append our param to the end.
        // Else, create a query string with our param.
        url = (queryParts[1] ? queryParts.join('?') + '&' : queryParts[0] + '?') + originParam;

        // Append the channel id so both Espany nodes can identify each other.
        // At this point, the channel id is the container's id, so grab it from there.
        url += '&' + CHANNEL_PARAM + '=' + this.el.id;

        // If the remote URL has a hash, append it.
        if (hashParts[1]) {
          url += ('#' + hashParts[1]);
        }

        return url;
      }

    };


    /**
     * Represents a remote procedure request preserved locally, containing callbacks
     * to be invoked when fulfulled. Callback methods can be simply added by
     * chaining them to a method invocation:
     *
     * var espany = new Espany({remote: 'path/to/remote'});
     * espany
     *   .myMethod() // returns a request object
     *   .result(function(data) {
     *     // do something with the result
     *   })
     *   .error(function(data) {
     *     // handle an error
     *   });
     *
     * @constructor
     * @param  {String}  method         The method the consumer wishes to invoke.
     * @param  {*}  params              The parameters the method should be invoked with.
     * @param  {Boolean} isNotification This message won't generate an id or response.
     */
    function Request(method, params, isNotification) {

      var payload = {
        method: method
      };

      // Params are optional.
      if (params) {
        payload.params = params;
      }

      // Non-notification requests need ids per the spec. Also, they match up the response
      // to fire the proper result/error callbacks.
      if (!isNotification) {
        this.id = payload.id = this._generateId();
      }

      // The exposed payload to be sent by a communication node.
      this.payload = payload;
    }

    Request.prototype = {

      /**
       * When a request response is received, this method is called and routs
       * the return data to the appropriate callback if one is set.
       * @param  {Object} data The response data returned from the remote call.
       */
      process: function(data) {
        if (data.result && this._resultCallback) {
          this._resultCallback(data.result);
        }
        else if (data.error && this._errorCallback) {
          this._errorCallback(data.error);
        }
      },

      /**
       * Sets a method to be called on request success.
       * @param  {Function} callback The method to be called.
       * @return {Request}           Reference to the Request instance.
       */
      result: function(callback) {
        this._resultCallback = callback;
        return this;
      },

      /**
       * Sets a method to be called on request error.
       * @param  {Function} callback The method to be called.
       * @return {Request}           Reference to the Request instance.
       */
      error: function(callback) {
        this._errorCallback = callback;
        return this;
      },

      /**
       * Generates a request id by incrementing an enclosed number variable.
       * @return {Number} A request id.
       */
      _generateId: (function() {
        var id = 0;
        return function() {
          return ++id;
        };
      }())

    };


    /**
     * This is Espany's public interface which proxies requests to the manager
     * and wrapped node for processing. When the discovery payload is received,
     * callable remote methods are created and attached to the Proxy object
     * to serve as the remote API.
     * @constructor
     * @param {Object} options A map of options used to configure Espany.
     *                         See the complete list in node.js.
     */
    function Proxy(options) {

      var self = this,
          ready = options.ready,
          node;

      /**
       * Called on ready, this method digests the available remote methods
       * and creates local methods to invoke them. Once these methods are
       * configured, this Proxy object will send it's callable methods to
       * it's remote counterpart if applicable.
       * @param  {String[]} methods A list of remote method names as strings.
       */
      function discover(methods) {

        var len = methods.length;

        /**
         * Creates a local proxy to a remote method that sends arguments appropriately
         * and sets up success and error callbacks.
         * @param  {String} method The remote method name.
         * @return {Function}      The local request object that you can chain callbacks to.
         */
        function buildMethod(method) {

          return function() {

            // Create a new request and send the payload off. But first,
            // convert the arguments to an array so we can send them via postMessage.
            var request = manager.addRequest(method, [].slice.call(arguments));

            // Send off the request.
            node.send(request.payload);

            // Return the request object to chain result and error callbacks.
            return request;
          };
        }

        // Loop through all the method names and build local methods for each.
        while (len--) {
          self[methods[len]] = buildMethod(methods[len]);
        }

        // If this is the consumer and has methods, send a discovery request.
        if (options.methods && node.container) {
          node._sendDiscovery();
        }
      }

      // Instantiate a manager if one does not exist.
      if (!manager) {
        manager = new Manager();
      }

      // If a ready method was included in the passed options, wrap it in a new
      // ready method that also calls discover.
      if (ready) {
        options.ready = function(methods) {
          discover(methods);
          ready();
        };
      }

      // Else, set discover as the ready method.
      else {
        options.ready = discover;
      }

      // Node contains all the transport code and is not exposed to the user.
      node = manager.addNode(options);

      /**
       * One of two public API methods that are not user defined, $destroy removes
       * the communication container and does some general cleanup.
       */
      this.$destroy = function() {
        manager.removeNode(node);
      };

      /**
       * The other public API method returns the communication container.
       * (Applicable in the parent document only).
       * @return {DOMElement} The communication iframe.
       */
      this.$container = function() {
        return node.container && node.container.el;
      };

      this.VERSION = '0.3.0';
    }


    win.Espany = Proxy;

  }(window));


  /* Includes */
  /**
   * $_CONSTANTS
   * Constants used throughout the framework. Be extremely careful when making changes to these constants.
   * Constants may be referenced externally (e.g. global events) by third-party integration.
   */
  var $_CONSTANTS = {

    // EVENTS
    events: {

      // framework (Global Events) - be careful with these
      frameworkLoaded:          'frameworkLoaded',
      systemReady:              'systemReady',
      auth:                     'auth',
      logout:                   'logout',
      refresh:                  'refresh',
      resize:                   'resize',
      noAuth:                   'noAuth',

      // window
      beforeUnload:             'beforeunload',
      unload:                   'unload',
      load:                     'load'
    },

    // STATISTICS
    stats: {
      trkKeyed:                 'cws-fwk-keyed',
      trkAnon:                  'cws-fwk-anonymous',
      eType:                    'widgetJSTracking',
      wType:                    'framework'
    },

    // TYPES
    types: {
      object:                   'object',
      string:                   'string',
      func:                     'function',
      number:                   'number',
      undef:                    'undefined',
      bool:                     'boolean',
      integer:                  'integer',
      list:                     'list',
      regex:                    'regex',
      html:                     'html',
      uiObject:                 'uiObject',
      globalEvent:              'globalEvent'
    }
  };

  var $_PATTERNS = {
    url:                (/^(http(s?)):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/i),   // fully qualified url
    domain:             (/^(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/i),                  // domain
    email:              (/^[a-z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-z0-9!#$%&'*+\/=?\^_`{|}~\-]+)*@(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/i),  // email
    protocols: {
      generic:          (/^http(s)?:\/\//i),                  // http and https protocols
      secure:           (/^https:\/\//i),                     // only https (ssl)
      nonSecure:        (/^http:\/\//i)                       // only http (non-ssl)
    },
    userAgents: {
      webkit:           (/Webkit|KHTML\//i),                  // webkit (chrome/safari)
      gecko:            (/Gecko\/([^\s]*)/i),                 // gecko (firefox)
      msie:             (/MSIE\s([^;]*)/),                    // ie
      iosAll:           (/OS .* like Mac OS X/i),             // all iOS platforms
      ios5:             (/OS 5_.* like Mac OS X/i),           // ios5
      ios6:             (/OS 6_.* like Mac OS X/i),           // ios6
      opera:            (/Opera[\s\/]([^\s]*)/)               // opera
    },
    context : {
      upperCase:       (/([A-Z])/g),                          // only uppercase
      lowerCase:       (/([a-z])/g)                           // only lowercase
    },
    types: {
      number:           (/^[0-9\.,]+$/),                      // number
      htmlAttribute:    (/^[a-z0-9\._\-]+$/i),                // html attributes
      token:            (/^[a-z0-9\.\-\_%]+$/i),              // oauth and member tokens, api_key, uid
      bool:             (/^(?:true|yes|1)$/i),                // regex for values that evaluate to true
      boolFalse:        (/^(?:false|no|0)$/i)                 // regex for values that explicitly evaluate to false
    },
    readyState:         (/(loaded|complete)/),                // ready state
    tags : {
      initialized:      (/\+init$/),                          // ends with "+init"
      attribute:        (/^data-/gi)
    },
    prefixes : {
      forwardSlash :    (/^\//),                              // starts with /
      urlEq:            (/^url=/i)                            // starts with url=
    },
    chars : {
      tilde:            (/^~$/),                              // starts with ~
      amp:              (/&/g),                               // &
      lt:               (/</g),                               // <
      gt:               (/>/g),                               // >
      quot:             (/"/g),                               // "
      squot:            (/'/),                                // '
      dot:              (/\./g),                              // an escaped regex for dots in URLs
      star:             (/\*/g)                               // a regex that represents * in wildcard domains
    }
  };


  /* Core */
  /**
   * Sets data at a specified path endpoint or resolves an endpoint by string.
   *
   * Examples:
   *
   *  1. Sets an endpoint:
   *
   *    _namespace('foo.bar', {
   *     food: 'tacos',
   *     good: true
   *    });
   *    IN.foo.bar.food; // -> 'tacos'
   *
   *  2. Resolves an endpoint:
   *
   *    _namespace('foo.bar'); // -> 'tacos'
   *
   *  3. Returns null if an endpoint cannot be resolved:
   *
   *    _namespace('baz.qux'); // -> null
   *
   * @param  {String|Object} pathOrContent The path you wish to set.
   *                                       If an object, the data is set on the base namespace (IN).
   *                                       If a string and content was not passed, use resolution mode.
   * @param  {Object} content       The data to set.
   * @return {Object}               The set data.
   */
  function _namespace(pathOrContent, content) {

    var memo = win.IN = win.IN || {},
        segments, segment, len, s;

    if (pathOrContent) {

      // If pathOrContent is a string, it's a path.
      // build the namespaces as necessary and assign the content to the final namespace.
      if (typeof pathOrContent === $_CONSTANTS.types.string) {
        segments = pathOrContent.split('.');
        len = segments.length;

        // If no content is set, reset the memo to window so we can resolve
        // methods there too.
        memo = (content) ? memo : win;

        // Loop through the namespaces to either set or resolve an object.
        for (s = 0; s < len; s++) {
          segment = segments[s];
          if (!memo[segment]) {

            // If content was passed, build up the namespace objects as necessary
            // or set the content if it's at the end.
            if (content) {
              memo[segment] = (s + 1 === len) ? content : {};
            }

            // Otherwise, we're in resolution mode. Return null if the namespace
            // is not found.
            else {
              return null;
            }
          }
          memo = memo[segment];
        }
      }

      // Else, the only other valid choice is an object.
      // In this case, assign content to the IN namespace.
      // NOTE: Previously-assigned keys will be rewritten.
      else {
        for (s in pathOrContent) {
          memo[s] = pathOrContent[s];
        }
      }
    }

    return memo;
  }

  _namespace({

    /*
     * Get a unique ID for this page load.
     */
    '$uid': (function() {
      var id = 0,
          prefix = "li_gen_";
      return function(prePrefix) {
        return ((prePrefix) ? prePrefix + '_' : '') + prefix + (new Date()).getTime() + '_' + (id++);
      };
    }())

  });

  // ----------------------------------------------------------------------------------------------------
  // Extensions
  // Begin Parallel loading of our extension files
  // ----------------------------------------------------------------------------------------------------
  (function() {

    // If IN.ENV doesn't exist for some reason, don't mess with extensions.
    if (!IN.ENV || !IN.ENV.js) {
      return;
    }

    var xtnList = IN.ENV.js.extensions || {},
        originalInExtensions = IN.$extensions,
        name,
        xtn,
        script;

    // Hotswap IN.$extensions with something more robust.
    IN.$extensions = function(defn, fn) {
      if (!fn) {
        return originalInExtensions(defn);
      }

      // Loading an extension via IN.$extensions.
      IN.Event.on(IN, $_CONSTANTS.events.frameworkLoaded, function() {
        fn();
        IN.ENV.js.extensions[defn].loaded = true;
      });
    };

    // Load all provided extensions.
    for (name in xtnList) {
      if (xtnList.hasOwnProperty(name)) {
        xtn = xtnList[name];
        if (xtn.loaded) {
          continue;
        }
        script = doc.createElement('script');
        script.type = 'text/javascript';
        script.src = xtn.src;
        IN.DOM.getByTag('head').appendChild(script);
      }
    }
  }());

  // ----------------------------------------------------------------------------------------------------
  // Set default param values and type-casting
  // ----------------------------------------------------------------------------------------------------
  if (IN.ENV && IN.ENV.js) {
    var TYPES = $_CONSTANTS.types,
        ENV_JS = IN.ENV.js,
        key,
        paramsMap = {
          authorize        : { type: TYPES.bool },
          debug            : { type: TYPES.bool },
          suppressWarnings : { type: TYPES.bool },
          deferParse       : { type: TYPES.bool, defaultValue: false },
          statistics       : { type: TYPES.bool, defaultValue: true },
          isFramed         : { type: TYPES.bool, defaultValue: (win.self !== win.parent) },
          lang             : { type: TYPES.string, defaultValue: "en_US" },
          scope            : { type: TYPES.list },
          noAuth           : { type: TYPES.bool }
        };

    // loop through to check for missing params and type-cast
    for (key in paramsMap) {

      if (paramsMap.hasOwnProperty(key)) {
        // type-cast
        if (typeof ENV_JS[key] !== TYPES.undef) {

          switch(paramsMap[key].type) {

            case TYPES.bool: // cast to boolean
              ENV_JS[key] = $_PATTERNS.types.bool.test(ENV_JS[key]);
              break;

            case TYPES.integer: // cast to integer
              ENV_JS[key] = parseInt(ENV_JS[key], 10);
              break;

            case TYPES.number: // cast to number
              ENV_JS[key] = Number(ENV_JS[key]);
              break;

            case TYPES.list:   // cast to array
              ENV_JS[key] = ENV_JS[key].replace(/(,|;|\s)/g, ' ') // make it space delimited
                                       .replace(/\s+/g, ' ')      // and remove double spaces
                                       .split(' ');               // and split it
              break;

            case TYPES.string: // don't cast, leave as a string
              /* falls through */
            default:
              break;
          }
        }
        // set default
        if ((typeof ENV_JS[key] === TYPES.undef) && (typeof paramsMap[key].defaultValue !== TYPES.undef)) {
          ENV_JS[key] = paramsMap[key].defaultValue;
        }
      }
    }
  }

  /**
   * Statistics
   * Ported from Connect
   */
  var $_STATISTICS;
  (function() {

    var EVENTS                  = $_CONSTANTS.events,
        ENV                     = IN.ENV,
        TYPE_TAGS               = 'tags',
        TYPE_APIS               = 'apis',
        TYPE_PROFILER           = 'profiler',
        TYPE_ENV                = 'env',
        TRACK_TYPES             = [TYPE_TAGS, TYPE_APIS, TYPE_PROFILER, TYPE_ENV],
        EMPTY_OBJECT_REGEX      = /([a-z_-])*\:\{\}(,)*/gi,   // Strip out any empty objects that got pushed onto the request.
        DANGLING_COMMA_REGEX    = /,\}/g,                     // Make sure there are no dangling commas.
        PARAM_REGEX             = '(\\?|&)([a-z]*)=({PARAM})',
        ORIGIN_PARAM_VALUE      = '__ORIGIN__',               // Referrer param value.
        WTYPE_PARAM_VALUE       = '__WTYPE__',                // Widget type param value.

        statsQueue              = ENV.statsQueue || [],
        isPingFired             = false,
        originParam             = new RegExp(PARAM_REGEX.replace(/\{PARAM\}/, ORIGIN_PARAM_VALUE), 'i'), // Pattern to match referrer param.
        wTypeParam              = new RegExp(PARAM_REGEX.replace(/\{PARAM\}/, WTYPE_PARAM_VALUE), 'i'), // Pattern to match widget type param.

        // Used for tracking.
        statistics = {
          env:      {},
          events:   {},
          tags:     {},
          apis:     {}
        },

        // Alias map (for minification of the payload) (see go/connect_webtrack for full alias reference)
        aliases = {
          types: {
            apis:               'a',
            tags:               't',
            profiler:           'p',
            env:                'e',
            action:             'a',
            count:              'c'
          },
          actions: {
            click:              'c'
          },
          apis: {
            raw:                'r',
            profile:            'p',
            group:              'g',
            connections:        'c',
            memberupdates:      'm',
            networkupdates:     'n',
            peoplesearch:       'ps',
            comment:            'co',
            post:               'po'
          },
          tags: {
            // Core tags
            share:              's',                        // IN/Share
            apply:              'a',                        // IN/Apply
            login:              'l',                        // IN/Login
            recommendproduct:   'r',                        // IN/Recommend
            companyprofile:     'cp',                       // IN/CompanyProfile
            companyinsider:     'ci',                       // IN/CompanyInsider
            memberdata:         'md',                       // IN/MemberData
            memberprofile:      'mp',                       // IN/MemberProfile
            fullmemberprofile:  'fmp',                      // IN/FullMemberProfile
            jymbii:             'j',                        // IN/JYMBII
            mail:               'm',                        // IN/Mail
            wizard:             'w',                        // IN/Wizard
            followcompany:      'fc',                       // IN/FollowCompany
            // Extensions
            employeeprofilewidget: 'xemp',                  // IN/EmployeeProfileWidget
            coreg:              'xcr',                      // IN/CoReg
            sfdc:               'xsf'                       // IN/SFDC
          },
          profiler: {
            'bl' : ['bootstrapInit', 'bootstrapLoaded'],        // bootstrap (in.js) loaded
            'be' : ['bootstrapInit', 'userspaceRequested'],     // bootstrap (in.js) completed execution
            'fl' : ['bootstrapInit', EVENTS.frameworkLoaded]    // complete framework load time (from in.js to frameworkLoaded)
          }
        };

    // If statistics are disabled, exit immediately.
    if (!ENV.js.statistics) {
      return;
    }

    /**
     * [getAvgDiff description]
     * @param  {[type]} array1 [description]
     * @param  {[type]} array2 [description]
     * @return {[type]}        [description]
     */
    function _getAvgDiff(array1, array2) {

      var i = Math.min(array1.length, array2.length),
          diff = 0,
          j = i;

      if (!array1 || !array2) {
        return NaN;
      }

      // Sum up the differences.
      for (;i--;) {
        diff += Math.abs(array1[i] - array2[i]);
      }

      // Return the average.
      return Math.ceil(diff/j);
    }


    function _buildProfilerData(data) {

      var profiler = aliases.profiler,
          key,
          value,
          diff;

      for (key in profiler) {
        if (profiler.hasOwnProperty(key)) {
          value = profiler[key];
          diff = _getAvgDiff(statistics.events[val[1]], statistics.events[val[0]]);
          // only include if it's non-zero and a real number
          if (!(diff === 0 || isNaN(diff) || !diff)) {
            data[key] = diff;
          }
        }
      }
    }

    function _buildAPIData(type, data) {

      var stats = statistics[type],
          key,
          entity,
          time;

      function extract(key, entity, dataType) {

        var collectedValues = null,
            values = stats[keys][dataType],
            value;

        for (value in values) {
          if (values.hasOwnProperty(value)) {
            collectedValues = collectedValues || {};
            collectedValues[aliases.actions[value] || value] = values[value];
          }
        }

        if (collectedValues) {
          entity[aliases.types[dataType]] = collectedValues;
        }
      }

      for (key in stats) {
        if (stats.hasOwnProperty(key)) {

          // include the entity identifier
          entity = currentDataSet[aliases[type][key]] = {};

          // extract the counts
          extract(key, entity, 'count');

          // extract the actions
          extract(key, entity, 'action');

          // if there are profiles associated with this, include them
          if (stats[key].profiler) {
            time = _getAvgDiff(stats[key].profiler.end, stats[key].profiler.start);
            entity[aliases.types.profiler] = (!(time === 0 || isNaN(time) || !time)) ? time : {};
          }
        }
      }
    }

    /**
     * [constructPing description]
     * @return {[type]} [description]
     */
    function _constructPing() {

      var i = TRACK_TYPES.length,
          data  = {},
          type,
          currentDataSet;

      for (i; i--;) {

        type = TRACK_TYPES[i];
        currentDataSet = data[aliases.types[type] || type] = {};

        // If profiler:
        if (type === TYPE_PROFILER) {
          _buildProfilerData(currentDataSet);
        }

        // If env:
        else if (type === TYPE_ENV) {
          // if isFramed, pass it along
          if (ENV.js.isFramed) {
            currentDataSet.f = 1;
          }
          // an apiKey was set, pass it along
          if (ENV.auth.api_key) {
            currentDataSet.a = ENV.auth.api_key;
          }
          // if an oauth token was delivered, pass it along
          if (ENV.auth.oauth_token) {
            currentDataSet.o = 1;
          }
        }

        // If apis or tags
        else if (type === TYPE_APIS || type === TYPE_TAGS) {
          _buildAPIData(type, currentDataSet);
        }

      }

      return JSON.stringify(data)
                    .replace($_PATTERNS.chars.quot, '')    // remove quotes
                    .replace(EMPTY_OBJECT_REGEX, '')       // remove empty objects
                    .replace(DANGLING_COMMA_REGEX, '}');   // remove dangling commas (need to check after removing empty objects)
    }

    /**
     * [getType description]
     * @param  {[type]} who [description]
     * @return {[type]}     [description]
     */
    function getType(who) {
      who = who.split(':');
      if (who.length < 2) {
        return {
          type : 'tags',
          who  : who[0].toLowerCase()
        };
      }
      return {
        type : who[0],
        who  : who[1].toLowerCase()
      };
    }

    function _instance(who, data) {

      var tmp = getType(who),
          tmpType = statistics[tmp.type],
          stats = tmpType[tmp.who] = tmpType[tmp.who] || {};

      if (!tmpType) {
        return;
      }

      // increment count
      stats.count = stats.count || {};
      // total count
      stats.count.t = 1 + (stats.count.t || 0);
      // do extra stuff
      if (data) {
        // if it's share and the URL points to another page, track it
        if (tmp.who == 'share' && data.url && data.url !== location.href) {
          // extra count
          stats.count.e = 1 + (stats.count.e || 0);
        }
      }
    }

    function _recordIt(record, who, what) {

      var temp = getType(who),
          stats;

      // if we're not tracking this type, just leave
      if(!statistics[temp.type]) {
        return;
      }

      stats = statistics[temp.type][temp.who];
      // if it's registered, then we can record it, otherwise this is an invalid attempt and we should skip it
      if (stats) {
        if (!stats[record]) {
          stats[record] = {};
        }
        if (!stats[record][what]) {
          stats[record][what] = [];
        }
        stats[record][what].push(+new Date());
      }
    }

    function _recordAction(who, action) {
    _recordIt('action', who, action);
    }

    function _recordEvent(event, timestamp) {
      var events = statistics.events;
      if (!events[event]) {
        events[event] = [];
      }
      events[event].push(timestamp || +new Date());
    }

    function _profile(who, isEnd) {
      var what = (isEnd) ? 'end' : 'start';
      _recordIt(TYPE_PROFILER, who, what);
    }

    function _firePing() {

      // we have to make sure the ping is fired only once, otherwise browsers that implement beforeunlaod and unload will fire it twice
      if (!isPingFired) {

        // construct the request and fire off the ping
        var ping = _constructPing(),
            // choose secure/nonSecure url based on protocol
            url = $_PATTERNS.protocols.secure.test(location.href) ? IN.ENV.url.analytics_url : IN.ENV.url.analytics_us_url,
            img = new Image(),
            CONSTS = $_CONSTANTS.stats,
            originParamMatch = url.match(originParam),
            wTypeParamMatch = url.match(wTypeParam);

        url += "&"+(+new Date()); // cache-burst

        // make sure referrer is at the end
        if(originParamMatch.length === 4) {
          // originParamMatch
          //  [1] = the delimiter (e.g. & | ?)
          //  [2] = the param name (e.g. or)
          //  [3] = the param value (same as ORIGIN_PARAM_VALUE, e.g. __ORIGIN__)
          var appendOrigin = '&'+originParamMatch[2]+'='+originParamMatch[3], // '&or=__ORIGIN__'
              replacer = (originParamMatch[1] === '?') ?  '?' : ''; // if the param was the first one, make sure we preserve the query delimiter (?)
          url = (url.replace(originParam, replacer) +     // remove the param entirely
                 appendOrigin)                        // append it to the end
                 .replace('?&', '?');                   // if it was originally the first param, we should clean up
        }
        // make sure wType is the first param, if it already is, skip this step
        if(wTypeParamMatch.length === 4 && wTypeParamMatch[1] !== '?') {
          // wTypeParamMatch
          //  [1] = the delimiter (e.g. & | ?)
          //  [2] = the param name (e.g. or)
          //  [3] = the param value (same as WTYPE_PARAM_VALUE, e.g. __WTYPE__)
          var insertParam = '?'+wTypeParamMatch[2]+'='+wTypeParamMatch[3]+'&';  // '?wt=__WTYPE__&'
          url = url.replace(wTypeParam, '')     // remove the param entirely
                   .replace('?', insertParam);  // insert it at the beginning
        }
        // fire off the actual request
        img.src = url.replace("__ETYPE__",    CONSTS.eType)
                     .replace("__TINFO__",    (IN.ENV.js.apiKey) ? CONSTS.trkKeyed : CONSTS.trkAnon)
                     .replace(WTYPE_PARAM_VALUE,  CONSTS.wType)
                     .replace("__TRKINFO__",  encodeURIComponent(ping))
                     .replace(ORIGIN_PARAM_VALUE, encodeURIComponent(location.href));
        isPingFired = true;
      }
    }

    // create external interface
    $_STATISTICS = {
      // register a tag/api call
      instance: _instance,
      // record an action
      recordAction: _recordAction,
      // record an event
      recordEvent: _recordEvent,
      // push onto the profiler
      profile: _profile,
      // fire off the ping
      firePing: _firePing
    };

    // process the queue
    for(var i=statsQueue.length; i--; ) {
      for(var key in statsQueue[i]) {
        if(statsQueue[i].hasOwnProperty(key)) {
          _recordEvent(key, statsQueue[i][key]);
        }
      }
    }

    // remove the stats queue
    IN.ENV.statsQueue = null;

    // register the unload event to fire off the ping
    // beforeunload is preferred, but if it doesn't fire, fallback on unload
    IN.Event.on(win, EVENTS.beforeUnload, $_STATISTICS.firePing);
    IN.Event.on(win, EVENTS.unload, $_STATISTICS.firePing);

  }());


  /* Event */
  /**
   * Events
   * Ported from Connect
   */
  (function() {

    // Blow away the bootstrap methods.
    IN.Event = null;

    var TYPES = $_CONSTANTS.types,
        eventRegistry = {},
        alsoOnLoad = null;

    function normalize(e) {

      if (!e.preventDefault) {
        e.preventDefault = function() {
          e.returnValue = false;
        };
      }
      if (!e.stopPropagation) {
        e.stopPropagation = function() {
          e.cancelBubble = true;
        };
      }
      if (!e.stopEvent) {
        e.stopEvent = function() {
          e.preventDefault();
          e.stopPropagation();
        };
      }

    }

    function globalEventHandler(elKey, action, e) {

      var handlers = eventRegistry[elKey][action],
          el = handlers.el,
          newHandlers = [],
          len = handlers.length,
          i = 0,
          handler;

      normalize(e);

      for (i; i < len; i++) {
        handler = handlers[i];
        handler.fn.call(handler.scope || el, e, handler.obj);
        if (!handler.fireOnce) {
          newHandlers.push(handler);
        }
      }

      // Clean up our fireOnce handlers by not preserving them
      eventRegistry[elKey][action] = newHandlers;
    }

    _namespace('Event', {

      remove: function(el, onAction, fn, scope, obj, fireOnce) {

        var normalizedAction = onAction.toLowerCase(),
            elKey,
            handlers,
            newHandlers,
            handler,
            method,
            evt,
            len,
            i;

        switch(this.getElType(el)) {

          // If 'string', get the element by id and fall through.
          case TYPES.string:
            el = IN.DOM.getById(el);
            /* falls through */
          case TYPES.html:
            elKey = this.getElKey(el);
            if (!eventRegistry[elKey] || !eventRegistry[elKey][normalizedAction]) {
              return;
            }

            handlers = eventRegistry[elKey][normalizedAction];
            newHandlers = [];
            for (i = 0, len = handlers.length; i < len; i++) {
              handler = handlers[i];
              if (handler.el !== el || handler.fn !== fn || handler.scope !== scope || handler.obj !== obj || handler.fireOnce !== fireOnce) {
                newHandlers.push(handler);
              }
            }
            eventRegistry[elKey][normalizedAction] = newHandlers;
          break;

          case TYPES.uiObject:
            try {
              method = 'un' + onAction.charAt(0).toUpperCase() + onAction.substr(1);
              if (el[method]) {
                el[method](fn, scope, obj, fireOnce);
              }
              else {
                el[method.toLowerCase()](fn, scope, obj, fireOnce);
              }
            } catch(e) {}
          break;

          case TYPES.globalEvent:
            evt = IN.GlobalEvents[onAction];
            if (!evt) {
              throw new Error('Global Event ' + onAction + ' is not defined.');
            }
            return evt.unsubscribe(fn, scope, obj, fireOnce);
        }

      },

      getElKey: function(el) {

        var id;

        if (!el.getAttribute) {
          return el;
        }

        id = el.id || el.getAttribute('data-IN-event-id');

        if (!id) {
          id = IN.$uid();
          el.setAttribute('data-IN-event-id', id);
        }

        return 'k' + id;
      },

      getElType: function(el) {

        if (typeof el === TYPES.string) {
          return TYPES.string;
        }

        if (el !== win && (typeof el  === TYPES.func || typeof el  === TYPES.object)) {
          try {
            if (el instanceof IN.Objects.Base) {
              return TYPES.uiObject;
            }
          } catch(e) {}
        }

        if (el === IN) {
          return TYPES.globalEvent;
        }

        return TYPES.html;
      },

      /**
       * Calls IN.Event.on with fireOnce set to true
       * @method onOnce
       * @param el {Object|HTMLElement|IN.Objects.Base} IN, extends IN.Objects.Base ,or an HTML Element
       * @param onAction {String} the action to listen to
       * @param fn {Function} a callback to run when "onAction" occurs
       * @param scope {Object} an optional scope to run "fn" in
       * @param obj {Object} an optonal object to pass through to "fn"
       */
      onOnce: function(el, onAction, fn, scope, obj) {
        return this.on(el, onAction, fn, scope, obj, true);
      },

      /**
       * attach an event listener to a provided object
       * @method on
       * @param el {Object|HTMLElement|IN.Objects.Base} IN, extends IN.Objects.Base ,or an HTML Element
       * @param onAction {String} the action to listen to
       * @param fn {Function} a callback to run when "onAction" occurs
       * @param scope {Object} an optional scope to run "fn" in
       * @param obj {Object} an optonal object to pass through to "fn"
       * @param fireOnce {Boolean} an optional value if TRUE the listener will fire only once
       */
      on: function(el, onAction, fn, scope, obj, fireOnce) {

        try {

          // If el is an array, call each subitem.
          if (IN.Util.isArray(el)) {
            for (var i = 0, len = el.length; i < len; i++) {
              IN.Event.on(el[i], onAction, fn, scope, obj, fireOnce);
            }
            return;
          }

          var type = this.getElType(el),
              isNew = false,
              normalizedAction = onAction.toLowerCase();

          switch (type) {

            // If 'string', set the element by id and fall through;
            case TYPES.string:
              el = IN.DOM.getById(el);
              /* falls through */
            case TYPES.html:
              var elKey = this.getElKey(el);

              if (!eventRegistry[elKey]) {
                eventRegistry[elKey] = {
                  el: el
                };
              }

              if (!eventRegistry[elKey][normalizedAction]) {
                eventRegistry[elKey][normalizedAction] = [];
                isNew = true;
              }

              eventRegistry[elKey][normalizedAction].push({
                fn: fn,
                scope: scope,
                obj: obj,
                fireOnce: fireOnce
              });

              if (isNew) {
                var callback = function(e) {
                  globalEventHandler(elKey, normalizedAction, e);
                };

                if (win.addEventListener && el.addEventListener) {
                  el.addEventListener(onAction, callback, false);
                }
                else if(el.attachEvent) {
                  el.attachEvent('on' + onAction, callback);
                }
                else {
                  console.log('could not bind event `' + onAction + '` to `' + elKey + '`');
                }
              }
            break;

            case TYPES.uiObject:
              try {
                var method = 'on' + onAction.charAt(0).toUpperCase() + onAction.substr(1);
                if (el[method]) {
                  el[method](fn, scope, obj, fireOnce);
                }
                else {
                  el[method.toLowerCase()](fn, scope, obj, fireOnce);
                }
              } catch(e) {}
            break;

            case TYPES.globalEvent:
              var evt = IN.GlobalEvents[onAction];
              if (!evt) {
                throw new Error("Global Event "+onAction+" is not defined.");
              }
              return evt.subscribe(fn, scope, obj, fireOnce);
          }
        }
        catch(e) {} // el is not accessible
      },

      /**
       * handle onDomReady. Based on the work of Dean Edwards, Et al
       * @method onDOMReady
       * @param fn {Function} a function to run onDomReady if you pass a literal "TRUE", you can force the DOM Ready events
       * @param scope {Object} a scope to run the "fn" in
       * @license BSD
       * @author YUI
       */
      onDOMReady: (function() {
        // event work based on the findings of Dean Edwards/Matthias Miller/John Resig
        // http://dean.edwards.name/weblog/2006/06/again/?full#comment5338
        var events = [],
            _timer = null,
            ready = false;

        // this is the public interface
        function add(fn, scope) {
          scope = scope || window;

          if (ready) {
            fn.call(scope);
            return;
          }
          else {
            events[events.length] = {//push?
              fn: fn,
              scope: scope
            };
          }
        }

        // init gets triggered by all our onload mehods
        // it then dispatches all "add" items
        function init() {

          var len = events.length,
              i = 0,
              evt;

          for (i; i < len; i++) {
            evt = events[i];
            evt.fn.call(evt.scope);
          }
        }

        // detect changes in DOM
        function onChange(e) {
          // if an event, and type is a DOMContentLoaded event, READY
          // if an event and type is load (window) event, READY
          if (e && (e.type == 'DOMContentLoaded' || e.type == 'load')) {
            fireDOMReady();
          }

          // document.readyState monitoring. If loaded OR complete, READY
          if (doc.readyState) {
            if (($_PATTERNS.readyState).test(doc.readyState)) {
              fireDOMReady();
            }
          }

          // IE not in an iframe (Diego's doScroll test) if doScoll doesn't
          // throw an exception, it's READY
          if (doc.documentElement.doScroll && win == win.top) {
            try {
              if (!ready) {
                doc.documentElement.doScroll('left');
              }
              fireDOMReady();
            }
            catch(err){}
          }
        }

        // handles firing the "dom ready" event. Cleans up handlers, invokes our
        // internal INIT function
        function fireDOMReady() {
          if (!ready) {
            ready = true;

            if (doc.removeEventListener) {
              doc.removeEventListener('DOMContentLoaded', onChange, false);
            }
            doc.onreadystatechange = null;

            clearInterval(timer);
            timer = null;

            // fire our init handling
            init();
          }
        }

        // LISTEN: addEventListener
        if (doc.addEventListener) {
          doc.addEventListener('DOMContentLoaded', onChange, false);
        }

        // LISTEN: On Ready State Change
        doc.onreadystatechange = onChange;

        // LISTEN: Check document state on interval, handles timer based checks
        timer = setInterval(onChange, 5);

        // LISTEN: onLoad fallback for browsers
        // if a window.onload is set, put it onto a normal window.onload queue
        if (win.onload) {
          var oldLoad = win.onload;
          if (IN.ENV.evtQueue) {
            IN.ENV.evtQueue.push({
              type: "on",
              args: [win, 'load', oldLoad]
            });
          }
          else {
            IN.Event.on(win, 'load', oldLoad);
          }
        }
        win.onload = onChange;

        // return the public interface
        return add;
      }())

    });

    // handle the event queue
    if (IN.ENV && IN.ENV.evtQueue) {
      for (var i = 0, len = IN.ENV.evtQueue.length; i < len; i++) {
        var item = IN.ENV.evtQueue[i],
            fn   = IN.Event[item.type],
            args = item.args;

        fn.apply(window, args);
      }
      IN.ENV.evtQueue = null;
    }

  }());

  // --------------------------------------------------------------------------------
  // LICENSES APPLIED TO THIS FILE
  /*
  Copyright (c) 2010, Yahoo! Inc.
  All rights reserved.
  Redistribution and use of this software in source and binary forms, with or
  without modification, are permitted provided that the following conditions are
  met:

  * Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

  * Neither the name of Yahoo! Inc. nor the names of its contributors may be used
  to endorse or promote products derived from this software without specific prior
  written permission of Yahoo! Inc.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */

  _namespace('CustomEvent',

    Fiber.extend(function() {

      return {

        init: function(name, occursOnlyOnce) {

          this.name = name;
          this.occursOnlyOnce = !!occursOnlyOnce;
          this.fired = false;
          this.firedArgs = [];
          this.events = [];

        },

        subscribe: function(fn, scope, obj, once) {

          var args = this.firedArgs,
              evt = {
                fn: fn,
                scope: scope,
                obj: obj,
                once: once
              };

          if (this.fired && this.occursOnlyOnce) {
            args.push(evt.obj || {});
          }
          else {
            this.events[this.events.length] = evt;
          }

        },

        unsubscribe: function(fn, scope, obj, once) {

          var events = this.events,
              newEvents = [],
              len = events.length,
              i = 0,
              evt;

          for (i; i < len; i++) {
            evt = events[len];
            if (evt.fn !== fn || evt.scope !== scope || evt.obj !== obj || evt.once !== once) {
              newEvents.push(evt);
            }
          }

          this.events = newEvents;
        },

        fire: function() {

          var copyArray = IN.Util.copyArray,
              events = [],
              len = this.events.length,
              i = 0,
              evt,
              args;

          if (this.fired && this.occursOnlyOnce) {
            return false;
          }

          this.firedArgs = copyArray(arguments);
          this.fired = true;

          for (i; i < len; i++) {
            evt = this.events[i];
            args = copyArray(arguments);
            args.push(evt.obj || {});

            if (!evt.once) {
              events.push(evt);
            }

            evt.fn.apply(evt.scope || win, args);
          }

          $_STATISTICS.recordEvent(this.name);
          this.events = events;

          return true;
        }

      };
    })
  );

  _namespace('GlobalEvents', (function() {

    var EVENTS = $_CONSTANTS.events,
        CustomEvent = IN.CustomEvent;

    return {
      auth: new CustomEvent(EVENTS.auth),
      noAuth: new CustomEvent(EVENTS.noAuth),
      logout: new CustomEvent(EVENTS.logout),
      refresh: new CustomEvent(EVENTS.refresh),
      systemReady: new CustomEvent(EVENTS.systemReady, true),
      frameworkLoaded: new CustomEvent(EVENTS.frameworkLoaded, true)
    };

  }()));


  /* More Core */
  _namespace('Util', {

    /**
     * Trims a string's whitespace by default or a given character.
     * @param  {String} str The string you wish to trim.
     * @param  {String} [chr=\\s] The character to trim.
     * @return {String} The trimmed string.
     */
    trim: function(str, chr) {
      chr = chr || '\\s';
      // This could be better. Chokes on regex-related characters (\, *, etc.).
      return str.replace(new RegExp('^(?:'+chr+')+|(?:'+chr+')+$', 'g'), '');
    },

    /**
     * Converts an object literal to a key/value string to be passed in a URL.
     * @param  {Object} obj The object literal to convert.
     * @return {String}     A URL parameter string converted from the passed object.
     */
    createParams: function(obj) {

      var TYPE = 'type',
          params = [],
          key,
          value,
          len,
          j;

      /**
       * Creates a URL key/value string pair.
       * @param  {String} key   The parameter's key.
       * @param  {String} value The parameter's value.
       */
      function parameterize(key, value) {
        params.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
      }

      for (key in obj) {
        value = obj[key];
        /*
         * This is needed since when using the MemberUpdates API, and passing a type parameter
         * with more than one value, we can't encode it like this: URL ? type=ONE,TWO.
         *
         * Instead LinkedIn's API requires us to pass multiple parameters with the same key,
         * like this: URL ? type=ONE&type=TWO.
         */
        if (key === TYPE && this.isArray(value)) {
          for (j = 0, len = value.length; j < len; j++) {
            parameterize(key, value[j]);
          }
        }
        else {
          parameterize(key, value);
        }
      }

      return params.join('&');
    },

    /**
     * Parse a set of URL Parameters
     * @method parseParams
     * @param query {String} the URL parameters to decode
     * @return {Object} the parsed key/value as an object literal
     */
    parseParams: function(query) {

      var params = {},
          piece, kvp, leftOfEqual, rightOfEqual;

      for (var i = 0, pieces = query.split('&'), len = pieces.length; i < len; i++) {
        piece = pieces[i];
        kvp = piece.split('=');
        leftOfEqual = decodeURIComponent(kvp.splice(0, 1));
        rightOfEqual = decodeURIComponent(kvp.join('='));
        params[leftOfEqual] = rightOfEqual;
      }

      return params;
    },

    /**
     * Test to see if the passed object is an array.
     * @param  {*}  obj The object you wish to test.
     * @return {Boolean}     Is the object an array?
     */
    isArray: function(obj) {
      return Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === '[object Array]';
    },

    /**
     * Test to see if the passed object is a POJSO.
     * @param  {*}  obj The object you wish to test.
     * @return {Boolean}     Is the object an object?
     */
    isObject: function(obj) {
      return Object.prototype.toString.call(obj) === '[object Object]';
    },

    /**
     * Shallow-copies a provided array.
     * @param  {Array} collection The array to copy.
     * @return {Array}            The new array.
     */
    copyArray: function(collection) {
      return [].slice.apply(collection);
    },

    /**
     * get the root URL for a provided URL
     * @method getRootURL
     * @param url {String} the url to parse
     * @return {String} the root URL
     */
    getRootURL: function(url) {
      var obj = this.getRootURLObject(url);
      return this.assembleRootURL(obj);
    },

    /**
     * get the root domain for a provided url
     * @method getRootDomain
     * @param url {String} the URL to parse
     * @return {String} the root domain
     */
    getRootDomain: function(url) {
      var obj = this.getRootURLObject(url);
      return obj.host;
    },

    /**
     * get the root URL for a provided URL as an object hash
     * @method getRootURLObject
     * @param url {String} the URL to parse
     * @return {Object} an object with protocol, host, and port
     */
    getRootURLObject: function(url) {
      url = url || location.href;

      if (url.indexOf('//') === 0) {
        url = win.location.protocol + url;
      }

      // put a protocol on the URL if not provided. It has to be same domain (think http/s)
      if (url.indexOf('://') === -1) {
        url = win.location.protocol + '//' + url;
      }

      // grab the host (greedy) from :// forward
      // also, grab our protocol (for sure) since we know we have ://
      var host = url.substring(url.indexOf('://') + 3);
      var protocol = url.substring(0, url.indexOf('://')).toLowerCase(); // lc to compare later

      // cut host down to first slash if available
      host = (host.indexOf('/') !== -1) ? host.substring(0, host.indexOf('/')) : host;

      // extract the port (if it is there)
      var hasPort = host.indexOf(':');
      var port = '';
      if (hasPort >= 0) {
        port = host.substring(hasPort + 1);
        host = host.substring(0, hasPort);
      }

      // did they go and do http :80 or https :443?
      // if so, clear the port string
      if ((port === '80' && protocol === 'http') || (port === '443' && protocol === 'https')) {
        port = '';
      }

      return {
        protocol: protocol,
        host: host,
        port: port
      };
    },

    /**
     * Calculate a CRC32 of a supplied string.
     * Based on Public Domain Code
     * http://www.naaccr.org/standard/crc32/crc32.c
     * http://www.csbruce.com/~csbruce/software/crc32.c
     * @param str {String} The string to calculate a crc32 for.
     * @return {Integer} The crc32.
     */
    crc32: (function() {

      var TABLE = [
        '00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4',
        'E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE',
        '1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9',
        'FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B',
        '35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A',
        'C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924',
        '2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F',
        '9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01',
        '6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950',
        '8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2',
        '4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5',
        'AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F',
        '5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6',
        '03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8',
        'E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB',
        '196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5',
        'D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C',
        '36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236',
        'CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31',
        '2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713',
        '95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242',
        '68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C',
        '8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7',
        '4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9',
        'BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8',
        '5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D'
      ].join(' ');

      return function(str) {

        var crc = 0,
            n = 0,
            x = 0,
            i = 0,
            len = str.length;

        crc = crc ^ (-1);

        for (i; i < len; i++) {
          n = (crc ^ str.charCodeAt(i)) & 0xFF;
          x = '0x' + TABLE.substr(n * 9, 8);
          crc = ( crc >>> 8 ) ^ x;
        }

        return crc ^ (-1);
      };
    }()),

    /**
     * Create a JavaScript Template object
     * Based on the code by John Resig (MIT License)
     * http://ejohn.org/blog/javascript-micro-templating/
     * @function IN.Util.createJSTemplate
     * @param str {String} the string to create a template from
     * @param templateMarkers {String} a space separated pair of markers used to denote JS code, defaults to <? and ?>
     * @return {Function} a callable function that takes an object literal as a paramter
     */
    createJSTemplate: (function() {

      var cache             = {},
          OPENING_JS        = '___JS_REPLACE___',
          CLOSING_JS        = '___END_JS_REPLACE___',
          TRIM_REGEX_START  = /^\n*/,
          TRIM_REGEX_END    = /\n*$/,
          ALL_QUOTE_REGEX   = /"/g,
          ECHO_STMT_REGEX   = /___JS_REPLACE___=(.+?)___END_JS_REPLACE___/g,
          CAPTURE_JS_QUOTE  = /("(?:(?!___JS_REPLACE___).)*)___END_JS_REPLACE___/g,
          ESCAPE_REGEX      = /[\-\[\]\{\}\(\)\*\+\?\.\,\^\$\|\#\s\\]/g,
          DEFAULT_MARKERS   = '<?js ?>';

      function quoteReplace(str) {
        return str.replace(ALL_QUOTE_REGEX, '\r');
      }

      function regEscape(str) {
        return str.replace(ESCAPE_REGEX, '\\$&');
      }

      return function(str, templateMarkers) {
        /*jslint evil: true */

        var key = 't' + Math.abs(IN.Util.crc32(str)),
            tmplSt,
            tmplEd,
            fn;

        if (cache[key]) {
          return cache[key];
        }

        if (!templateMarkers && IN.ENV && IN.ENV.js && IN.ENV.js.templateMarkers) {
          templateMarkers = IN.ENV.js.templateMarkers;
        }

        templateMarkers = templateMarkers || DEFAULT_MARKERS;
        templateMarkers = templateMarkers.split(' ');
        tmplSt = templateMarkers[0];
        tmplEd = templateMarkers[1];

        if (!tmplSt || !tmplEd) {
          throw new Error('Template markers must be set.');
        }

        if (tmplSt === tmplEd) {
          throw new Error('Start and end markers cannot be identical.');
        }

        tmplSt = new RegExp(regEscape(tmplSt), 'g');
        tmplEd = new RegExp(regEscape(tmplEd), 'g');

        fn = ['',
          'var p=[],',                                      // p = private array
          '$=function(v) {',                                // define a local output function
            'return (v == "*") ? obj : obj[v];',            // used to get complex keys from the object
          '},',                                             // ie $('http://www.example.com')
          'print=function() {',                             // define a local print function
            'p.push.apply(p, arguments);',                  // print pushes the value onto the print stack
          '};',                                             // end of print function
          'with(obj) {',                                    // (E) with() localizes obj scope (See E)
            'try {',                                        // (D) begin a try block (see D)
              'p.push("',                                   // (B) start by pushing on to p (see B)
                str.replace(tmplSt, OPENING_JS)             // replace opening <? with our JS_REPLACE constant
                .replace(tmplEd, CLOSING_JS)                // replace closing ?> with our JS_REPLACE ending constant
                .split('\r').join('')                       // (C) eliminate all windows newline characters (see C)
                .split('\t').join('    ')                   // replace 'tab' with 4 spaces (in case it is significant)
                .replace(TRIM_REGEX_START, '')              // remove leading and trailing \n
                .replace(TRIM_REGEX_END, '')                // remove leading and trailing \n
                .split('\n').join('\\n')                    // replace all newline characters with an escaped newline
                .replace(CAPTURE_JS_QUOTE, quoteReplace)    // (A)(C) get quotes that are in JS statements, and swap to \r
                .split('"').join('\\"')                     // escape " marks in tmpl
                .split('\r').join('"')                      // (A) \r to single quotes (see A)
                .replace(ECHO_STMT_REGEX, '",$1,"')         // <?js= foo ?> to ',foo,' with newlines preserved
                .split(OPENING_JS).join('");')              // (B) raw opening <?js to ');, causing the p.push to end (see B)
                .split(CLOSING_JS).join('p.push("'),        // (B) any remaining closings to p.push(' (see B)
              '");',                                        // (B) to the end, terminate the push (see B)
            '}',                                            // (D) end try (see D)
            'catch(_tmplEx_) {',                            // (D) catch TMPL exceptions caused by missing var (see D)
              'p.push("Error: ", _tmplEx_.message)',        // log the error into the p() space
            '}',                                            // (D) end catch statement (see D)
          '}',                                              // (E) end with() (see E)
          'return p.join("");',                             // (B) return p.join(''); the array we just made (see B)
        ''].join('');

        cache[key] = new Function('obj', fn);
        return cache[key];
      };
    }()),

    /**
     * Validate developer specified parameters against a parameter mask.
     * @param params {Object} developer provided attributes
     * @param paramsMask {Object} a validation mask to test against
     * @return params {Object} validated parameters
     */
    validateAttributes: function(params, paramsMask) {

      // locally map as each param goes through validation so we don't
      // have to test it multiple times
      var VALIDATED = 'isValid_' + (+new Date()),
          error;

      function isValid(key) {

        // if we already tested it, don't test it again, just return our previous evalutation
        if (typeof paramsMask[key][VALIDATED] !== $_CONSTANTS.types.undef) {
          return paramsMask[key][VALIDATED];
        }

        var transform = paramsMask[key].transform;

        if (transform) {
          try {
            params[key] = transform(params[key]);
            paramsMask[key][VALIDATED] = true;
            return true;
          }
          catch(e) {
            error = checkAgainst.invalidError || key+' is not a valid '+key+' ({0})';
            throw new Error( IN.Util.formatString(error, params[key]) );
          }
        }

        var value = params[key],
            match = paramsMask[key].match;

        // if there isn't anything to match against, it's valid
        // if there is no value for the parameter, it's valid (no value is a good value)
        // (missing or empty values are handled by the main loop)
        if(!match || !value) {
          return true;
        }

        var matchType = typeof match,
            didPass = true,
            TYPES = $_CONSTANTS.types;
        matchType = (match instanceof RegExp) ? 'regex' : matchType;
        // matchType can be function, string, or regex
        // if it's a string, use it as a regex pattern
        switch(matchType) {
        case TYPES.func:
          didPass = match(value);
          break;
        case TYPES.string:
          // convert a string to a regex pattern
          match = new Regex(match);
          // don't break, and pass the new regex pattern along for testing
          /* falls through */
        case TYPES.regex:
          value = params[key];
          didPass = match.test(value);
          break;
        default:
          break;
        }
        paramsMask[key][VALIDATED] = didPass;
        return didPass;
      }


      for( var key in paramsMask ) {
        if( paramsMask.hasOwnProperty(key) ) {
          var checkAgainst = paramsMask[key];
          var valid = isValid(key);
          // if the parameter isn't valid, throw an error for the developer
          if(!valid) {
            error = checkAgainst.invalidError || key+' is not a valid '+key+' ({0})';
            throw new Error( IN.Util.formatString(error, params[key]) );
          }
          // use default value if no value or bad value provided
          if( checkAgainst.defaultValue && (!valid || !params[key]) ) {
            // set the default value and continue (default values should always be valid)
            params[key] = checkAgainst.defaultValue;
            continue;
          }

          if( checkAgainst.required === true) {
            if( params[key] && valid ) {
              continue;
            }
            else {
              // check if alternate value is present
              var missing = true;
              if( checkAgainst.exception ) {
                for(var i=checkAgainst.exception.length; i--; ) {
                  var eKey = checkAgainst.exception[i];
                  // if one of the alternate values is present, we don't need this one, so we can skip it
                  if(params[eKey] && isValid(eKey) ) {
                    missing = false;
                    break;
                  }
                }
              }
              if(missing) {
                error = checkAgainst.error || key+' was not provided';
                throw new Error(error);
              }
            }
          }
        }
      }
      return params;
    }

  });

  _namespace('DOM', {

    /**
     * Fetches an element by id.
     * @param  {String} id The id of the element to fetch.
     * @return {HTMLElement} The element.
     */
    getById: function(id) {
      return doc.getElementById(id);
    },

    /**
     * Fetches elements by tag name from a container.
     * @param  {String} tag  The tag to match.
     * @param  {Object} [root=document] The containing element to fetch from.
     * @return {HTMLCollection} The matched elements.
     */
    getByTag: function(tag, root) {
      root = root || doc;
      return root.getElementsByTagName(tag);
    }

  });


  /* Tags */
  var TagParser = Fiber.extend(function() {

    var tagPatterns = $_PATTERNS.tags;

    return {

      init: function() {
        this.tagRegistry = {};
      },

      /**
       * [normalize description]
       * @param  {[type]} node [description]
       * @return {[type]}      [description]
       */
      _normalize: function(node) {

        var tagName = node.tagName.toLowerCase(),
            name,
            type,
            pieces;

        if (tagName === 'in:tag') {
          name = node.getAttribute('name').toLowerCase();

          // If it's already initialized, exit.
          if (tagPatterns.initialized.test(name)) {
            return false;
          }
        }

        else if (tagName === 'script') {
          type = node.type.toLowerCase();
          pieces = type.split('/');

          // If it's already initialized or not part of the framework, exit.
          if (tagPatterns.initialized.test(type) || pieces[0] != "in") {
            return false;
          }

          name = pieces[1];
        }

        // Unregistered tags are skipped for now.
        if (!this.tagRegistry[name]) {
          return false;
        }

        // set to init: IMPORTANT, must come after check for existance
        if (type) {
          node.type = node.type + "+init";
        }
        else {
          node.setAttribute("name", name+"+init");
        }

        return name;
      },

      processTagMatch: function(node) {

        var name = this._normalize(node),
            contents = node.innerHTML,
            attr = {},
            attrs = node.attributes,
            len = attrs.length,
            attrib,
            attrName,
            i = 0,
            dataPrefix = (IN.ENV.js.dataNamespace) ?
                                'data-'+(IN.ENV.js.dataNamespace.replace(tagPatterns.attribute, '').toLowerCase())+'-' :
                                'data-';

        // If normalize failed, return immediately.
        if (!name) {
          return false;
        }

        for (i; i < len; i++) {
          attrib = attrs.item(i);
          attrName = attrib.nodeName;
          if (attrName.indexOf(dataPrefix) === 0) {
            if (attrib.specified) {
              attrName = attrName.replace(dataPrefix, '').toLowerCase();
              attr[attrName] = attrib.nodeValue;
            }
          }
        }

        attr.type = node.type;
        attr.content = contents;

        // push onto the statistics stack
        $_STATISTICS.instance(name, attr);

        // invoke
        new this.tagRegistry[name](node, attr);

        return true;
      },

      parse: function(node) {

        var self = this;

        function parseTagType(type) {

          var tags = IN.DOM.getByTag(type, node),
              len = tags.length,
              i = 0;

          for (i; i < len; i++) {
            if (tags[i]) {
              self.processTagMatch(tags[i]);
            }
          }
        }

        parseTagType('script');
        parseTagType('in:tag');
      },

      add: function(name, obj) {
        this.tagRegistry[name.toLowerCase()] = obj;
      }

    };
  }),

  parser = new TagParser();

  _namespace({

    addTag: function(name, base, classFunc) {
      parser.add(name, _namespace('Tags.' + name,
        IN.Tags[base].extend(classFunc)
      ));
    },

    parse: function(node) {
      parser.parse(node);
    }

  });

  IN.Event.on(IN, $_CONSTANTS.events.systemReady, function() {
    parser.parse(doc.body);
  });

  _namespace('Tags.Base',

    Fiber.extend(function() {

      function buildContainer() {

        var container = doc.createElement('span'),
            containerStyle = container.style;

        containerStyle.lineHeight = '1';
        containerStyle.verticalAlign = 'baseline';
        containerStyle.display = 'inline-block';
        container.className = 'IN-widget';

        return container;
      }

      function buildRemoteContainer(config) {

        var options = {

              // An ID to identify this transport client.
              channel: IN.$uid(),

              // The URL that will render the container's content. The v2 query param is
              // appended so widgets can reuse their legacy endpoints easier.
              remote: config.url + '?v2=true',

              // Default methods all remote widget content will have access to.
              methods: {

                /**
                 * Returns the functionality-providing endpoint for widget interaction.
                 * For instance, the login widget's endpoint is an authorization document.
                 * @return {String} The endpoint URL.
                 */
                getEndpoint: function() {
                  return config.endpoint;
                },

                /**
                 * Resize the iframe to the provided dimensions, in pixels.
                 * @param  {Number} width  The new width.
                 * @param  {Number} height The new height.
                 * @return {Boolean}       Was the resize successful?
                 */
                resize: function(width, height) {

                  // 'this' is in the Espany object context.
                  var style = this.container.el.style;

                  if (width === 0 || width) {
                    style.width = width + 'px';
                  }
                  if (height === 0 || height) {
                    style.height = height + 'px';
                  }
                  return true;
                }
              },

              // Attributes the transport iframe will have.
              container: {
                className: 'IN-widget',
                scrolling: 'no',
                allowtransparency: 'true',
                frameBorder: 0,
                border: 0,
                width: 1,
                height: 1
              }
            },
            configMethods = config.methods,
            optionMethods = options.methods,
            method;

        // If other methods were passed in the config, add them.
        if (configMethods) {
          for (method in configMethods) {
            optionMethods[method] = configMethods[method];
          }
        }

        // Create and return the RPC transport object.
        return new Espany(options);
      }

      return {

        init: function(name, node, attributes) {
          this.name = name;
          this.node = node;
          $_STATISTICS.profile(name);
        },

        createContainer: function(remoteConfig) {
          var container;
          if (remoteConfig) {
            this.rpc = buildRemoteContainer(remoteConfig);
            container = this.rpc.$container();
          }
          else {
            container = buildContainer();
          }
          this.node.parentNode.insertBefore(container, this.node);
          this.container = container;
          return container;
        },

        /**
         * Parse a passed attribute or a comma-separated list of attributes.
         * @param  {String} attr The attribute to parse.
         * @param  {String} type The attribute's type.
         * @return {Array} A collection of parsed attributes.
         */
        parseAttribute: function(attr, type) {

          var i = 0,
              len;

          attr = attr ? attr.split(',') : [];
          len = attr.length;

          for (i; i < len; i++) {
            attr[i] = IN.Util.trim(attr[i]);
            if (type === 'callback') {
              attr[i] = _namespace(attr[i]);
            }
          }
          return attr;
        },

        /**
         * Creates a JS template from the provided string.
         * @param  {String} str The string to create the template from.
         * @return {Function} Invocation context for a template, takes "data" as param.
         */
        createTemplate: function(str) {
          return IN.Util.createJSTemplate(str);
        },

        validateAttributes: function(params, paramsMask) {
          return IN.Util.validateAttributes(params, paramsMask);
        }

      };

    })

  );

  IN.addTag('Login', 'Base', function(base) {

    return {

      init: function(node, attributes) {

        // Extract all the callbacks passed.
        var callbacks = {
          auth: this.parseAttribute(attributes.onauth, 'callback'),
          logout: this.parseAttribute(attributes.onlogout, 'callback')
        };

        base.init.call(this, 'Login', node, attributes);

        // Create a template if content exists.
        this.tmpl = attributes.content ? this.createTemplate(attributes.content) : false;

        // Set event listeners and configure callback listeners if available.
        this.listen(callbacks);

        // Render the logged out state if the user is not authorized.
        if (!IN.User.isAuthorized()) {
          this.renderUnauthorizedState();
        }

        // end the profiler.
        // boilerplate ... try to remove
        $_STATISTICS.profile(this.name, true);
      },

      listen: function(callbacks) {

        var callback, list, len, i;

        for (callback in callbacks) {
          list = callbacks[callback];
          for (i = 0, len = list.length; i < len; i++) {
            IN.Event.on(IN, callback, list[i]);
          }
        }

        IN.Event.on(IN, $_CONSTANTS.events.auth, function() {
          this.renderAuthorizedState();
        }, this);

        IN.Event.on(IN, $_CONSTANTS.events.logout, function() {
          this.renderUnauthorizedState();
        }, this);
      },

      // Thinking... perhaps this should be available outside of the Login widget as before.
      getAuthURL: function(silent) {

        var url = IN.ENV.url,
            type = silent ? url.silent_auth_url : url.authorize;

        return [
          type,
          type.indexOf('?') === -1 ? '?' : '&',
          'client_id=' + IN.ENV.js.apiKey,
          '&type=user-agent',
          '&v2=true'
        ].join('');
      },

      renderAuthorizedState: function() {

        // Container reference.
        var container;

        // If a Login button exists already, destroy it to make room for
        // the template, if applicable.
        if (this.rpc) {
          this.rpc.$destroy();
        }

        // If a template was included, create a container and render it.
        if (this.tmpl) {

          // Create a new, plain container.
          container = this.createContainer();

          // Call the API and render the results in the container.
          IN.API.Profile('me')
            .result(function(result) {
              container.innerHTML = this.tmpl(result.values[0]);
            }, this)
            .error(function(result) {
              container.innerHTML = result.message;
            }, this);
        }
      },

      /**
       * If the user is not logged in, display the login button widget.
       */
      renderUnauthorizedState: function() {

        var config = {
          url: IN.ENV.widget.login_url,
          endpoint: this.getAuthURL(),
          methods: {
            authorize: function(oauthToken, memberId, oauthCookie) {
              IN.User.setAuthorized(oauthToken, memberId, oauthCookie);
            }
          }
        };

        this.createContainer(config);
      }

    };

  });


  /* API */
  /**
   * API
   * Public JS interface for accessing the developer API.
   */
  (function() {

    var methods = ['Profile', 'Connections', 'NetworkUpdates', 'MemberUpdates', 'Raw'],
        len = methods.length,
        method,
        queue;

    _namespace('API', {

      get: function(name) {

        var api = new IN.APIs[name]();

        function interval() {
          win.setTimeout(function() {
            queue.dispatch.call(queue, true);
            interval();
          }, IN.ENV.api.queueInterval);
        }

        if (!queue) {
          queue = new IN.APIQueue();
          interval();
        }

        api.invoke = function() {
          queue.invoke.apply(queue, arguments);
        };

        return api;
      },

      PeopleSearch: function() {
        var api = IN.API.get('PeopleSearch');
        if (arguments.length) {
          throw new Error('PeopleSearch does not take any arguments.');
        }
        return api;
      }

    });

    function buildAPIMethod(method) {
      _namespace('API.' + method, function() {
        var api = IN.API.get(method);
        api.ids.apply(api, arguments);
        return api;
      });
    }

    while (len--) {
      buildAPIMethod(methods[len]);
    }

  }());

  _namespace('APIQueue',

    Fiber.extend(function() {

      var rpc = null,
          rpcReady = false;

      return {

        init: function() {
          this.callQueue = [];
          this.maxQueue = 10;
        },

        invoke: function(resource, method, params, postBody, callbacks) {

          var dummy = function(){},
              onSuccessCallback = callbacks.onSuccess || dummy,
              onFailureCallback = callbacks.onFailure || dummy,
              callbackScope = callbacks.scope || win,
              types = $_CONSTANTS.types;

          function extractResponse(response) {
            var data;
            if (typeof response === types.string && response !== '' &&  response !== 'null') {
              try {
                data = JSON.parse(response);
              } catch(e) {
                data = {
                  message: 'Error while parsing the server response to JSON (check \'raw_message\' property for details)',
                  raw_message: response
                };
              }
            }
            return data;
          }

          function callback(response) {
            var data = extractResponse(response);
            if (typeof data.raw_message !== types.undef || typeof data.errorCode !== types.undef) {
              onFailureCallback.call(callbackScope, data);
              return;
            }
            onSuccessCallback.call(callbackScope, data);
          }

          function callbackFailure(response) {
            response = response.message;
            var data = extractResponse(response);
            onFailureCallback.call(callbackScope, data);
          }

          // attach to Params all the IN.ENV things we need
          params.oauth_token = IN.ENV.auth.oauth_token || IN.ENV.auth.anonymous_token;

          this.queue({
            resource: IN.Util.trim(IN.ENV.url.api, '/') + '/' + IN.Util.trim(resource, '/'),
            method: method,
            params: params,
            postBody: postBody,
            callback: callback,
            failureCallback: callbackFailure
          });
        },

        queue: function(payload) {
          this.callQueue.push(payload);
          this.dispatch();
        },

        dispatch: function(force) {

          var self = this;

          if (!rpc) {
            rpc = new Espany({
              remote: IN.ENV.url.api_xd_html_v2 + '#mode=cors',
              ready: function() {
                rpcReady = true;
                self.dispatch(force);
              }
            });
          }

          else if (this._isReady(force)) {
            this._sendRequests();
          }
        },

        // for each item in the queue, dispatch an ajax call.
        // when we support boxcarring requests, this loop will create the super-payload and then
        // call a different method on transport
        _sendRequests: function() {

          var callQueue = this.callQueue,
              len = callQueue.length,
              i = 0,
              item;

          for (i; i < len; i++) {
            item = callQueue[i];

            // RPC
            rpc
              .ajax({
                method: item.method,
                url: item.resource,
                params: item.params,
                postBody: item.postBody
              })
              .result(item.callback)
              .error(item.failureCallback);
          }

          // reset call queue to 0
          this.callQueue = [];
        },

        _isReady: function(force) {

          var queueLength = this.callQueue.length;

          return (
            // If the API communication is ready,
            rpcReady &&
            // and we're either forcing or the queue limit is reached,
            (force || queueLength >= this.maxQueue)
            // then we're good to go!
          );
        }

      };
    })
  );

  _namespace('APIs.Base',

    Fiber.extend(function() {

      var isArray = IN.Util.isArray,
          copyArray = IN.Util.copyArray;

      return {

        init: function() {

          this.isLinked = false;
          this.useParams = {};
          this.useMethod = 'GET';
          this.postBody = null;
          this.executed = false;

          this.storedSuccessResults = false;
          this.storedFailureResults = false;

          this.type = 'Base';

          this.handlers = {
            raw: [],
            errors: []
          };
        },

        _checkExecuted: function() {
          if (this.executed) {
            throw new Error('Command has executed. You can\'t append additional properties.');
          }
        },

        /**
         * Set query string params onto the API Object
         * @method params
         * @param p {Object} an object literal containing key/values to set
         * @return {Object}
         */
        params: function(p) {

          var name;

          this._checkExecuted();

          if (!(p instanceof Object)) {
            throw new Error('params() must receive a key-value-pair like object.');
          }

          for (name in p) {
            this.useParams[name] = p[name];
          }

          return this;
        },

        /**
         * Sets a param into the param object
         * @method setParam
         * @param name {String} the name of the param to set
         * @param value {Object} the value to set
         * @param preserveCase {Boolean} if true, case is preserved, for example, on ID keys
         * @return {this}
         */
        setParam: function(name, args, preserveCase) {

          var list = copyArray(args),
              len = list.length,
              i = 0,
              item;

          this._checkExecuted();

          // handle the different formats the list can be in
          if (isArray(list[0])) {
            list = list[0];
          }
          else if (!isArray(list)) {
            list = list.split(',');
          }

          // loop through all items in the list
          for (i; i < len; i++) {
            item = list[i];
            list[i] = this.transformField(item, preserveCase);
          }

          // if there were any items we looped through, save them
          if (list.length) {
            this.useParams[name] = list;
          }

          return this;
        },

        /**
         * set the method type to use for this call
         * @method method
         * @param type {String} the HTTP method to use, e.g. POST, GET
         * @return {Object}
         */
        method: function(type) {
          this._checkExecuted();
          this.useMethod = type.toUpperCase();
          return this;
        },

        /**
         * set the message body (used in POST or PUT calls)
         * @method body
         * @param body {String} the post body
         * @return {Object}
         */
        body: function(body) {
          this._checkExecuted();
          this.postBody = body;
          return this;
        },

        /**
         * return the raw results from a call
         * @method result
         * @param fn {Function|Object} the callback function
         * @param scope {Object} the scope to run the function in
         * @return {Object}
         */
        result: function() {
          $_STATISTICS.instance('apis:' + this.type);
          this.addHandler(this.handlers.raw, copyArray(arguments));
          this.get();
          return this;
        },

        /**
         * Register an error handler for this call
         * @method error
         * @param fn {Function|Object} the callback function
         * @param scope {Object} the scope to run the function in
         * @return {Object}
         */
        error: function() {
          this.addHandler(this.handlers.errors, copyArray(arguments));
          this.get();
          return this;
        },

        /**
         * ABSTRACT return the name of the API to use
         * @method name
         * @protected
         * @return {String}
         */
        name: function() {
          throw new Error('name() must be defined');
        },

        /**
         * ABSTRACT return the resource string for this object
         * @method resource
         * @protected
         * @return {String}
         */
        resource: function() {
          throw new Error('resource() must be defined');
        },

        /**
         * get the HTTP Method for submitting this API
         * @method getMethod
         * @protected
         * @return {String}
         */
        getMethod: function() {
          return this.useMethod;
        },

        /**
         * get the Params collection. Used to avoid reading useParams outside
         * of this object
         * @protected
         * @method getParams
         * @return {Object}
         */
        getParams: function() {
          return this.useParams;
        },

        /**
         * gets the Post Body
         * @method getPostBody
         * @private
         * @return {String}
         */
        getPostBody: function() {
          return this.postBody || '';
        },

        /**
         * Get the resource part of the URL for an API
         * @method getResource
         * @private
         * @param nested {Boolean} if true the resource will be based on being nested
         * @return {String}
         */
        getResource: function(nested) {

          var str = this.resource(nested),
              useParams = this.getParams(),
              name,
              regex,
              par;

          for (name in useParams) {
            regex = new RegExp('\\{' + name.toUpperCase() + '\\}', 'g');
            par = isArray(useParams[name]) ? useParams[name].join(',') : useParams[name];
            str = str.replace(regex, par);
          }

          // clean up empty {}
          str = str.replace(/\{.*?\}/g, '');

          // clean up empty segments, clean up empty chaining
          str = str.replace(/::\([,]*\)/g, '').replace(/:\([,]*\)/g, '');
          str = str.replace(/:\([,]*/g, ":(").replace(/[,]*\)/g, ')').replace(/,,+/, ',');

          // trim any stray colons
          str = IN.Util.trim(str, ':');

          return str;
        },

        /**
         * Get the parameters for an API call
         * anything not used in the resource string is considered a parameter
         * @private
         * @method getParameters
         * @param nested {Boolean} if true, the parameters will be based on being nested
         * @return {Object}
         */
        getParameters: function(nested) {

          var str = this.resource(nested),
              useParams = this.getParams(),
              out = {},
              size = 0,
              name,
              regex;

          for (name in useParams) {
            regex = new RegExp('\\{' + name.toUpperCase() + '\\}', 'g');
            if (!str.match(regex)) {
              out[name] = useParams[name];
              size++;
            }
          }

          if (nested) {
            for (name in nested) {
              out[name] = nested[name];
              size++;
            }
          }

          return (size) ? out : false;
        },

        /**
         * adds a handler with a provided scope to the handler queue
         * @method addHandler
         * @private
         * @param handler {Array} the handler reference
         * @param args {Array} a function/scope combo. if 2 params, second is scope
         * @return {Object}
         */
        addHandler: function(handler, args) {

          var scope = win,
              fn = function(){};

          if (args[1]) {
            scope = args[1];
          }

          if (args[0]) {
            fn = args[0];
          }

          handler.push({
            fn: fn,
            scope: scope,
            ran: false
          });

          if (this.storedSuccessResults) {
            this.handleSuccessResults(this.storedSuccessResults);
          }

          if (this.storedFailureResults) {
            this.handleFailureResults(this.storedFailureResults);
          }
        },

        /**
         * runs a collection of handlers for the specified value, avoiding running them a second time
         * @method runHandler
         * @private
         * @param handler {Array} an array of handlers to run
         * @param value {Object} the object to pass to all handlers
         * @param metadata {Object} the metadata object to pass to all handlers
         */
        runHandler: function(handler, value, metadata) {

          var len = handler.length,
              i = 0,
              item;

          for (i; i < len; i++) {
            item = handler[i];
            if (!item.ran) {
              item.fn.call(item.scope, value, metadata);
            }
          }
        },

        /**
         * marks a handler as ran, so that it won't run again
         * @method markHandlerAsRan
         * @private
         * @param handler {String} the handler type to mark
         */
        markHandlerAsRan: function(handler) {
          var len = handler.length;
          while (len--) {
            handler[len].ran = true;
          }
        },

        /**
         * Handler for successful results in this call
         * @method handleSuccessResults
         * @private
         * @param results {Object} the results of the call
         * @return {Object}
         */
        handleSuccessResults: function(results) {

          var raw = this.handlers.raw;
          this.storedSuccessResults = results;

          // run and clear the raw handlers
          this.runHandler(raw, results);
          this.markHandlerAsRan(raw);

          return this;
        },

        /**
         * Handler for failed results in this call
         * @method handleFailureResults
         * @private
         * @param results {Object} the results of the call
         * @return {Object}
         */
        handleFailureResults: function(results) {
          this.storedFailureResults = results;
          this.runHandler(this.handlers.errors, results);
        },

        /**
         * run the API, calling success() if defined
         * @method get
         * @private
         * @param success {Function} a callback function to run on API success
         * @param scope {Object} a scope to run the callback in
         * @param returnStyle {String} a return style "each", "first", "all"
         * @return {this}
         */
        get: function() {

          if (this.executed || this.isLinked) {
            return this;
          }
          this.executed = true;

          // get the key/value pairs and the parameter string
          var kvp = this.getParameters() || {},
              rsc = this.getResource(),
              method = this.getMethod(),
              postBody = this.getPostBody();

          this.invoke(rsc, method, kvp, postBody, {
            onSuccess: this.handleSuccessResults,
            onFailure: this.handleFailureResults,
            scope: this
          });
          return this;
        },

        /**
         * Transforms a field or collection of fields into a PAL compatible format
         * @method transformField
         * @private
         * @param field {String|Object|Array} the field
         * @param preserveCase {Boolean} if true, case is preserved
         * @return {String}
         */
        transformField: function(field, preserveCase) {

          var out = null;

          if (typeof field === $_CONSTANTS.types.string) {

            out = field;

            // convert the special "me" field
            if (out === 'me') {
              out = '~';
            }

            // urls must be encoded
            if ($_PATTERNS.prefixes.urlEq.test(out)) {
              out = 'url=' + encodeURIComponent(out.replace($_PATTERNS.prefixes.urlEq, ''));
            }

            // if it was a URL, convert to URL format
            if ($_PATTERNS.protocols.generic.test(out)) {
              out = 'url=' + encodeURIComponent(out);
            }

            // split camel case items if not preserving case
            if (!preserveCase) {
              out = out.replace($_PATTERNS.context.upperCase, '-$1').toLowerCase();
            }
          }
          // is this an API
          else if (field instanceof IN.APIs.Base) {
            out = field.getResource(true);
          }

          // if this is an array
          else if (isArray(field)) {
            out = [];
            for (var i = 0, len = field.length; i < len; i++) {
              out.push(this.transformField(field[i]));
            }

            out = out.join(',');
          }

          // is this item an object
          else if (IN.Util.isObject(field)) {
            out = [];
            for (var name in field) {
              out.push(name + ':(' + this.transformField(field[name]) + ')');
            }
            out = out.join(',');
          }

          // unsupported
          else {
            throw new Error('Unknown Type: ' + field);
          }

          return out;
        }

      };
    })
  );

  _namespace('APIs.Profile',

    IN.APIs.Base.extend(function(base) {

      var copyArray = IN.Util.copyArray;

      return {

        init: function() {
          base.init.call(this, arguments);
          this.defaultFields = ['id', 'first-name', 'last-name', 'headline', 'picture-url'];
          this.setParam('fields', this.defaultFields);
          this.type = 'Profile';
        },

        /**
         * Set the ids to retrieve
         * @method ids
         * @param idList {Array|String} an array or string of ids to get
         * @return {this}
         */
        ids: function() {
          return this.setParam('ids', copyArray(arguments), true);
        },

        /**
         * Set fields onto this API object
         * @method fields
         * @param {String|Object} a series of strings or objects defining fields
         * @return {Object}
         */
        fields: function() {
          return this.setParam('fields', copyArray(arguments));
        },

        /**
         * see IN.APIs.Base.name()
         * @method name
         * @protected
         */
        name: function() {
          return 'people.get';
        },

        resource: function() {

          var ids = this.getParams().ids,
              len = ids.length;

          if (!ids || !len) {
            throw new Error('ids must be set either through the constructor or through .ids()');
          }

          if (!IN.ENV.auth.oauth_token) {
            while (len--) {
              if (ids[len] === '~') {
                throw new Error("You need to authenticate in order to use 'me' or '~' as a valid profile identifier");
              }
            }
          }

          return "/people::({IDS}){ISPUBLIC}:({FIELDS})";
        }

      };
    })
  );


  /* Even More Core */
  /**
   * User
   * Mostly ported from Connect with some pruning.
   */
  _namespace('User', {

    setAuthorized: function(oauthToken, memberId, oauthCookieValue) {
      IN.ENV.auth.oauth_token = oauthToken;
      IN.ENV.auth.member_id = memberId;
      this.setOauthCookie(oauthCookieValue);

      // fire global event. Placed on setTimeout to take
      // its place appropriately on the global event stack
      window.setTimeout(function authTimeout() {
        IN.GlobalEvents.auth.fire();
        IN.GlobalEvents.refresh.fire();
      }, 10);
    },

    setNotAuthorized: function() {
      IN.GlobalEvents.noAuth.fire();
    },

    setLoggedOut: function() {
      IN.ENV.auth.oauth_token = '';
      IN.ENV.auth.member_id = '';
      this.setOauthCookie('');

      // fire global event. Placed on setTimeout to take
      // its place appropriately on the global event stack
      win.setTimeout(function logoutTimeout() {
        IN.GlobalEvents.logout.fire();
      }, 10);
    },

    setOauthCookie: function() {

      var env = IN.ENV,
          cookieName = 'linkedin_oauth_' + env.auth.api_key;

      if (!env.auth.is_set_client_auth_cookie || oauthCookieValue === '' || oauthCookieValue === null) {
        win.setTimeout(function clearCookie() {
          doc.cookie = cookieName + '=null;path=/;secure;expires=0';
          doc.cookie = cookieName + '_crc=null;path=/;expires=0';
        }, 0);
        return;
      }

      // is what we have legit JSON (unencoded) (if we end up with an already encoded string, this fails)
      if (typeof oauthCookieValue === $_CONSTANTS.types.string) {
        try {
          oauthCookieValue = JSON.parse(oauthCookieValue);
        }
        catch(e) {}
      }

      // in cases where we weren't clearing the cookie, we need to
      // clean up the cookie value we want to save by stringifying it
      if (typeof oauthCookieValue === $_CONSTANTS.types.object) {
        oauthCookieValue = encodeURIComponent(JSON.stringify(oauthCookieValue));
      }

      // set as a session cookie per Mike's request
      // use window.setTimeout to decouple in case this is invoked by a JSONP call
      // set a CRC to validate cookie on non HTTPS environments
      win.setTimeout(function setCookie() {
        doc.cookie = cookieName + '=' +oauthCookieValue + ';path=/;secure;';
        //document.cookie = cookieName+'set=true;path=/;';
        if (env.js.credentialsCookieCrc) {
          doc.cookie = cookieName + '_crc=' + IN.Util.crc32(oauthCookieValue) + ';path=/;';
        }
      }, 10);

    },

    isAuthorized: function() {
      return !!(IN.ENV.auth.oauth_token || IN.ENV.auth.oauth_verified);
    },

    getUIMode: function() {
      var modes = $_CONSTANTS.modes;
      return this.isAuthorized() ? modes.iframe : modes.window;
    },

    getMemberId: function() {
      return (IN.ENV && IN.ENV.auth && IN.ENV.auth.member_id) ? IN.ENV.auth.member_id : '';
    }

  });

  /**
   * OnLoad
   * Ported from Connect
   */
  // ----------------------------------------------------------------------------------------------------
  // End of Framework, Trigger Onloads
  // ----------------------------------------------------------------------------------------------------
  (function() {

    var MAX_WAIT = 4000,
        TRY_INTERVAL = 20,
        attempts = Math.floor(MAX_WAIT / TRY_INTERVAL),
        timedOut = false,
        timeoutPingReady,
        timeoutExtension;

    // do the onloads if set up in the env
    function fireOnLoads() {

      var TYPE_FUNC = $_CONSTANTS.types.func,
          i = 0,
          len,
          callbacks,
          callback;

      if (IN.ENV.js.onLoad) {
        callbacks = IN.ENV.js.onLoad.split(',');
        len = callbacks.length;
        for (i; i < len; i++) {
          callback = _namespace(IN.Util.trim(callbacks[i]));
          if (callback && typeof callback === TYPE_FUNC) {
            callback();
          } else {
            throw new Error('Could not execute "' + callbacks[i] + '". Please provide a valid function for callback.');
          }
        }
      }
    }

    // when the timeout has been reached, this reports on what extensions haven't loaded
    function extensionTimeout() {

      var extensions = IN.ENV.js.extensions,
          notLoaded = [],
          name;

      // set global timeout as having occured
      timedOut = true;

      // collect a list of failed extensions
      for (name in extensions) {
        if (!extensions[name].loaded) {
          notLoaded.push(name + ' @ ' + (extensions[name].src || 'linkedin'));
        }
      }

      console.log('The following extensions did not load: ' + notLoaded.join(', '));
    }

    // this checks all extensions to see if they have been properly loaded
    function checkSystemReady() {

      var silentAuth = IN.ENV.url.silent_auth_url.indexOf('$') === -1,
          extensions = IN.ENV.js.extensions,
          name;

      if (timedOut) {
        return true;
      }

      if (!IN.ENV.js || !IN.ENV.js.extensions) {
        return true;
      }

      // only test for auth results if silent_auth is available
      if (silentAuth && IN.ENV.js.authorize) {
        return false;
      }

      // check each extension for its loaded state
      for (name in extensions) {
        if (!extensions[name].loaded) {
          return false;
        }
      }

      // all successful
      return true;
    }

    // a recurring function that looks for a ready system. If it is ready, then it cancels the
    // larger extension timeout and fires the systemReady event
    function pingSystemReady() {

      if (checkSystemReady()) {
        win.clearTimeout(timeoutPingReady);
        IN.Event.onDOMReady(function fireSystemReady() {
          IN.GlobalEvents.systemReady.fire();
        });
      }

      else {
        attempts--;
        if (attempts <= 0) {
          extensionTimeout();
        }
        else {
          timeoutPingReady = win.setTimeout(pingSystemReady, 10);
        }
      }
    }

    // the framework is loaded. Do all hotpatching
    IN.GlobalEvents.frameworkLoaded.fire();

    // create an XD listener for the login channel
    // binds the login event to the global event and captures the access_token
    // for use throughout the IN platform
    IN.Event.on(IN, $_CONSTANTS.events.systemReady, function() {
      // fire the login or logout event onload based on IN.ENV value. By using a setTimeout,
      // we make sure it'll fire after the rest of the JS has loaded (and all onDOMReady has gone)
      if (IN.ENV.auth.oauth_token) {
        win.setTimeout(function() {
          IN.GlobalEvents.auth.fire();
        }, 10);
      }
    });

    // on systemReady, we fire all the onLoad handlers from the ENV
    IN.Event.on(IN, $_CONSTANTS.events.systemReady, fireOnLoads);

    // fire the system ready pinger when the DOM is complete
    // this makes sure if our FWK is loaded, we don't jump the gun in manipulating the DOM
    pingSystemReady();

    // parse any tags we have so far
    if (!IN.ENV.js.deferParse) {
      IN.parse(document.body);
    }

  }());


  win.namespace = _namespace;

}(window));
