text: var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]};
  var dataCount = 1;
  
  var util = {
    noop: function() {},
  
    CLOUD_HOST: '0.peerjs.com',
    CLOUD_PORT: 9000,
  
    // Browsers that need chunking:
    chunkedBrowsers: {'Chrome': 1},
    chunkedMTU: 16300, // The original 60000 bytes setting does not work when sending data from Firefox to Chrome, which is "cut off" after 16384 bytes and delivered individually.
  
    // Logging logic
    logLevel: 0,
    setLogLevel: function(level) {
      var debugLevel = parseInt(level, 10);
      if (!isNaN(parseInt(level, 10))) {
        util.logLevel = debugLevel;
      } else {
        // If they are using truthy/falsy values for debug
        util.logLevel = level ? 3 : 0;
      }
      util.log = util.warn = util.error = util.noop;
      if (util.logLevel > 0) {
        util.error = util._printWith('ERROR');
      }
      if (util.logLevel > 1) {
        util.warn = util._printWith('WARNING');
      }
      if (util.logLevel > 2) {
        util.log = util._print;
      }
    },
    setLogFunction: function(fn) {
      if (fn.constructor !== Function) {
        util.warn('The log function you passed in is not a function. Defaulting to regular logs.');
      } else {
        util._print = fn;
      }
    },
  
    _printWith: function(prefix) {
      return function() {
        var copy = Array.prototype.slice.call(arguments);
        copy.unshift(prefix);
        util._print.apply(util, copy);
      };
    },
    _print: function () {
      var err = false;
      var copy = Array.prototype.slice.call(arguments);
      copy.unshift('PeerJS: ');
      for (var i = 0, l = copy.length; i < l; i++){
        if (copy[i] instanceof Error) {
          copy[i] = '(' + copy[i].name + ') ' + copy[i].message;
          err = true;
        }
      }
      err ? console.error.apply(console, copy) : console.log.apply(console, copy);  
    },
    //
  
    // Returns browser-agnostic default config
    defaultConfig: defaultConfig,
    //
  
    // Returns the current browser.
    browser: (function() {
      if (window.mozRTCPeerConnection) {
        return 'Firefox';
      } else if (window.webkitRTCPeerConnection) {
        return 'Chrome';
      } else if (window.RTCPeerConnection) {
        return 'Supported';
      } else {
        return 'Unsupported';
      }
    })(),
    //
  
    // Lists which features are supported
    supports: (function() {
      if (typeof RTCPeerConnection === 'undefined') {
        return {};
      }
  
      var data = true;
      var audioVideo = true;
  
      var binaryBlob = false;
      var sctp = false;
      var onnegotiationneeded = !!window.webkitRTCPeerConnection;
  
      var pc, dc;
      try {
        pc = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]});
      } catch (e) {
        data = false;
        audioVideo = false;
      }
  
      if (data) {
        try {
          dc = pc.createDataChannel('_PEERJSTEST');
        } catch (e) {
          data = false;
        }
      }
  
      if (data) {
        // Binary test
        try {
          dc.binaryType = 'blob';
          binaryBlob = true;
        } catch (e) {
        }
  
        // Reliable test.
        // Unfortunately Chrome is a bit unreliable about whether or not they
        // support reliable.
        var reliablePC = new RTCPeerConnection(defaultConfig, {});
        try {
          var reliableDC = reliablePC.createDataChannel('_PEERJSRELIABLETEST', {});
          sctp = reliableDC.reliable;
        } catch (e) {
        }
        reliablePC.close();
      }
  
      // FIXME: not really the best check...
      if (audioVideo) {
        audioVideo = !!pc.addStream;
      }
  
      // FIXME: this is not great because in theory it doesn't work for
      // av-only browsers (?).
      if (!onnegotiationneeded && data) {
        // sync default check.
        var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]});
        negotiationPC.onnegotiationneeded = function() {
          onnegotiationneeded = true;
          // async check.
          if (util && util.supports) {
            util.supports.onnegotiationneeded = true;
          }
        };
        var negotiationDC = negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST');
  
        setTimeout(function() {
          negotiationPC.close();
        }, 1000);
      }
  
      if (pc) {
        pc.close();
      }
  
      return {
        audioVideo: audioVideo,
        data: data,
        binaryBlob: binaryBlob,
        binary: sctp, // deprecated; sctp implies binary support.
        reliable: sctp, // deprecated; sctp implies reliable data.
        sctp: sctp,
        onnegotiationneeded: onnegotiationneeded
      };
    }()),
    //
  
    // Ensure alphanumeric ids
    validateId: function(id) {
      // Allow empty ids
      return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id);
    },
  
    validateKey: function(key) {
      // Allow empty keys
      return !key || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(key);
    },
  
  
    debug: false,
  
    inherits: function(ctor, superCtor) {
      ctor.super_ = superCtor;
      ctor.prototype = Object.create(superCtor.prototype, {
        constructor: {
          value: ctor,
          enumerable: false,
          writable: true,
          configurable: true
        }
      });
    },
    extend: function(dest, source) {
      for(var key in source) {
        if(source.hasOwnProperty(key)) {
          dest[key] = source[key];
        }
      }
      return dest;
    },
    pack: BinaryPack.pack,
    unpack: BinaryPack.unpack,
  
    log: function () {
      if (util.debug) {
        var err = false;
        var copy = Array.prototype.slice.call(arguments);
        copy.unshift('PeerJS: ');
        for (var i = 0, l = copy.length; i < l; i++){
          if (copy[i] instanceof Error) {
            copy[i] = '(' + copy[i].name + ') ' + copy[i].message;
            err = true;
          }
        }
        err ? console.error.apply(console, copy) : console.log.apply(console, copy);
      }
    },
  
    setZeroTimeout: (function(global) {
      var timeouts = [];
      var messageName = 'zero-timeout-message';
  
      // Like setTimeout, but only takes a function argument.	 There's
      // no time argument (always zero) and no arguments (you have to
      // use a closure).
      function setZeroTimeoutPostMessage(fn) {
        timeouts.push(fn);
        global.postMessage(messageName, '*');
      }
  
      function handleMessage(event) {
        if (event.source == global && event.data == messageName) {
          if (event.stopPropagation) {
            event.stopPropagation();
          }
          if (timeouts.length) {
            timeouts.shift()();
          }
        }
      }
      if (global.addEventListener) {
        global.addEventListener('message', handleMessage, true);
      } else if (global.attachEvent) {
        global.attachEvent('onmessage', handleMessage);
      }
      return setZeroTimeoutPostMessage;
    }(this)),
  
    // Binary stuff
  
    // chunks a blob.
    chunk: function(bl) {
      var chunks = [];
      var size = bl.size;
      var start = index = 0;
      var total = Math.ceil(size / util.chunkedMTU);
      while (start < size) {
        var end = Math.min(size, start + util.chunkedMTU);
        var b = bl.slice(start, end);
  
        var chunk = {
          __peerData: dataCount,
          n: index,
          data: b,
          total: total
        };
  
        chunks.push(chunk);
  
        start = end;
        index += 1;
      }
      dataCount += 1;
      return chunks;
    },
  
    blobToArrayBuffer: function(blob, cb){
      var fr = new FileReader();
      fr.onload = function(evt) {
        cb(evt.target.result);
      };
      fr.readAsArrayBuffer(blob);
    },
    blobToBinaryString: function(blob, cb){
      var fr = new FileReader();
      fr.onload = function(evt) {
        cb(evt.target.result);
      };
      fr.readAsBinaryString(blob);
    },
    binaryStringToArrayBuffer: function(binary) {
      var byteArray = new Uint8Array(binary.length);
      for (var i = 0; i < binary.length; i++) {
        byteArray[i] = binary.charCodeAt(i) & 0xff;
      }
      return byteArray.buffer;
    },
    randomToken: function () {
      return Math.random().toString(36).substr(2);
    },
    //
  
    isSecure: function() {
      return location.protocol === 'https:';
    }
  };
  
  exports.util = util;
  