util.js 7.8 KB

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