util.ts 7.9 KB

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