var defaultConfig = { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }; var dataCount = 1; import BinaryPack from "js-binarypack"; import { RTCPeerConnection } from "./adapter"; export const util = { noop: function() {}, CLOUD_HOST: "0.peerjs.com", CLOUD_PORT: 443, // 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; } }; 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; })(window), // Binary stuff // chunks a blob. chunk: function(bl) { var chunks = []; var size = bl.size; var index; 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:"; } };