util.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]};
  2. var dataCount = 1;
  3. var util = {
  4. noop: function() {},
  5. CLOUD_HOST: '0.peerjs.com',
  6. CLOUD_PORT: 9000,
  7. 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. I suspect a bug in either Chrome or Firefox behind this.
  8. // Logging logic
  9. logLevel: 0,
  10. setLogLevel: function(level) {
  11. var debugLevel = parseInt(level, 10);
  12. if (!isNaN(parseInt(level, 10))) {
  13. util.logLevel = debugLevel;
  14. } else {
  15. // If they are using truthy/falsy values for debug
  16. util.logLevel = level ? 3 : 0;
  17. }
  18. util.log = util.warn = util.error = util.noop;
  19. if (util.logLevel > 0) {
  20. util.error = util._printWith('ERROR');
  21. }
  22. if (util.logLevel > 1) {
  23. util.warn = util._printWith('WARNING');
  24. }
  25. if (util.logLevel > 2) {
  26. util.log = util._print;
  27. }
  28. },
  29. setLogFunction: function(fn) {
  30. if (fn.constructor !== Function) {
  31. util.warn('The log function you passed in is not a function. Defaulting to regular logs.');
  32. } else {
  33. util._print = fn;
  34. }
  35. },
  36. _printWith: function(prefix) {
  37. return function() {
  38. var copy = Array.prototype.slice.call(arguments);
  39. copy.unshift(prefix);
  40. util._print.apply(util, copy);
  41. };
  42. },
  43. _print: function () {
  44. var err = false;
  45. var copy = Array.prototype.slice.call(arguments);
  46. copy.unshift('PeerJS: ');
  47. for (var i = 0, l = copy.length; i < l; i++){
  48. if (copy[i] instanceof Error) {
  49. copy[i] = '(' + copy[i].name + ') ' + copy[i].message;
  50. err = true;
  51. }
  52. }
  53. err ? console.error.apply(console, copy) : console.log.apply(console, copy);
  54. },
  55. //
  56. // Returns browser-agnostic default config
  57. defaultConfig: defaultConfig,
  58. //
  59. // Returns the current browser.
  60. browser: (function() {
  61. if (window.mozRTCPeerConnection) {
  62. return 'Firefox';
  63. } else if (window.webkitRTCPeerConnection) {
  64. return 'Chrome';
  65. } else if (window.RTCPeerConnection) {
  66. return 'Supported';
  67. } else {
  68. return 'Unsupported';
  69. }
  70. })(),
  71. //
  72. // Lists which features are supported
  73. supports: (function() {
  74. if (typeof RTCPeerConnection === 'undefined') {
  75. return {};
  76. }
  77. var data = true;
  78. var audioVideo = true;
  79. var binaryBlob = false;
  80. var sctp = false;
  81. var onnegotiationneeded = !!window.webkitRTCPeerConnection;
  82. var pc, dc;
  83. try {
  84. pc = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]});
  85. } catch (e) {
  86. data = false;
  87. audioVideo = false;
  88. }
  89. if (data) {
  90. try {
  91. dc = pc.createDataChannel('_PEERJSTEST');
  92. } catch (e) {
  93. data = false;
  94. }
  95. }
  96. if (data) {
  97. // Binary test
  98. // Commented out the "binary blob" feature detection. Firefox supports sending Blobs, whereas Chrome does not. Hence we agree on the common denominator (which is using plain ArrayBuffers).
  99. /*
  100. try {
  101. dc.binaryType = 'blob';
  102. binaryBlob = true;
  103. } catch (e) {
  104. }
  105. */
  106. // Reliable test.
  107. // Unfortunately Chrome is a bit unreliable about whether or not they
  108. // support reliable.
  109. var reliablePC = new RTCPeerConnection(defaultConfig, {});
  110. try {
  111. var reliableDC = reliablePC.createDataChannel('_PEERJSRELIABLETEST', {});
  112. sctp = reliableDC.reliable;
  113. } catch (e) {
  114. }
  115. reliablePC.close();
  116. }
  117. // FIXME: not really the best check...
  118. if (audioVideo) {
  119. audioVideo = !!pc.addStream;
  120. }
  121. // FIXME: this is not great because in theory it doesn't work for
  122. // av-only browsers (?).
  123. if (!onnegotiationneeded && data) {
  124. // sync default check.
  125. var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]});
  126. negotiationPC.onnegotiationneeded = function() {
  127. onnegotiationneeded = true;
  128. // async check.
  129. if (util && util.supports) {
  130. util.supports.onnegotiationneeded = true;
  131. }
  132. };
  133. var negotiationDC = negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST');
  134. setTimeout(function() {
  135. negotiationPC.close();
  136. }, 1000);
  137. }
  138. if (pc) {
  139. pc.close();
  140. }
  141. return {
  142. audioVideo: audioVideo,
  143. data: data,
  144. binaryBlob: binaryBlob,
  145. binary: sctp, // deprecated; sctp implies binary support.
  146. reliable: sctp, // deprecated; sctp implies reliable data.
  147. sctp: sctp,
  148. onnegotiationneeded: onnegotiationneeded
  149. };
  150. }()),
  151. //
  152. // Ensure alphanumeric ids
  153. validateId: function(id) {
  154. // Allow empty ids
  155. return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id);
  156. },
  157. validateKey: function(key) {
  158. // Allow empty keys
  159. return !key || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(key);
  160. },
  161. debug: false,
  162. inherits: function(ctor, superCtor) {
  163. ctor.super_ = superCtor;
  164. ctor.prototype = Object.create(superCtor.prototype, {
  165. constructor: {
  166. value: ctor,
  167. enumerable: false,
  168. writable: true,
  169. configurable: true
  170. }
  171. });
  172. },
  173. extend: function(dest, source) {
  174. for(var key in source) {
  175. if(source.hasOwnProperty(key)) {
  176. dest[key] = source[key];
  177. }
  178. }
  179. return dest;
  180. },
  181. pack: BinaryPack.pack,
  182. unpack: BinaryPack.unpack,
  183. log: function () {
  184. if (util.debug) {
  185. var err = false;
  186. var copy = Array.prototype.slice.call(arguments);
  187. copy.unshift('PeerJS: ');
  188. for (var i = 0, l = copy.length; i < l; i++){
  189. if (copy[i] instanceof Error) {
  190. copy[i] = '(' + copy[i].name + ') ' + copy[i].message;
  191. err = true;
  192. }
  193. }
  194. err ? console.error.apply(console, copy) : console.log.apply(console, copy);
  195. }
  196. },
  197. setZeroTimeout: (function(global) {
  198. var timeouts = [];
  199. var messageName = 'zero-timeout-message';
  200. // Like setTimeout, but only takes a function argument. There's
  201. // no time argument (always zero) and no arguments (you have to
  202. // use a closure).
  203. function setZeroTimeoutPostMessage(fn) {
  204. timeouts.push(fn);
  205. global.postMessage(messageName, '*');
  206. }
  207. function handleMessage(event) {
  208. if (event.source == global && event.data == messageName) {
  209. if (event.stopPropagation) {
  210. event.stopPropagation();
  211. }
  212. if (timeouts.length) {
  213. timeouts.shift()();
  214. }
  215. }
  216. }
  217. if (global.addEventListener) {
  218. global.addEventListener('message', handleMessage, true);
  219. } else if (global.attachEvent) {
  220. global.attachEvent('onmessage', handleMessage);
  221. }
  222. return setZeroTimeoutPostMessage;
  223. }(this)),
  224. // Binary stuff
  225. // chunks a blob.
  226. chunk: function(bl) {
  227. var chunks = [];
  228. var size = bl.size;
  229. var start = index = 0;
  230. var total = Math.ceil(size / util.chunkedMTU);
  231. while (start < size) {
  232. var end = Math.min(size, start + util.chunkedMTU);
  233. var b = bl.slice(start, end);
  234. var chunk = {
  235. __peerData: dataCount,
  236. n: index,
  237. data: b,
  238. total: total
  239. };
  240. chunks.push(chunk);
  241. start = end;
  242. index += 1;
  243. }
  244. dataCount += 1;
  245. return chunks;
  246. },
  247. blobToArrayBuffer: function(blob, cb){
  248. var fr = new FileReader();
  249. fr.onload = function(evt) {
  250. cb(evt.target.result);
  251. };
  252. fr.readAsArrayBuffer(blob);
  253. },
  254. blobToBinaryString: function(blob, cb){
  255. var fr = new FileReader();
  256. fr.onload = function(evt) {
  257. cb(evt.target.result);
  258. };
  259. fr.readAsBinaryString(blob);
  260. },
  261. binaryStringToArrayBuffer: function(binary) {
  262. var byteArray = new Uint8Array(binary.length);
  263. for (var i = 0; i < binary.length; i++) {
  264. byteArray[i] = binary.charCodeAt(i) & 0xff;
  265. }
  266. return byteArray.buffer;
  267. },
  268. randomToken: function () {
  269. return Math.random().toString(36).substr(2);
  270. },
  271. //
  272. isSecure: function() {
  273. return location.protocol === 'https:';
  274. }
  275. };
  276. exports.util = util;