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