util.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import * as BinaryPack from "js-binarypack";
  2. import { RTCPeerConnection } from "./adapter";
  3. const DEFAULT_CONFIG = {
  4. iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
  5. sdpSemantics: "unified-plan"
  6. };
  7. /*
  8. Prints log messages depending on the debug level passed in. Defaults to 0.
  9. 0 Prints no logs.
  10. 1 Prints only errors.
  11. 2 Prints errors and warnings.
  12. 3 Prints all logs.
  13. */
  14. export enum DebugLevel {
  15. Disabled,
  16. Errors,
  17. Warnings,
  18. All
  19. }
  20. export class util {
  21. static noop(): void { }
  22. static readonly CLOUD_HOST = "0.peerjs.com";
  23. static readonly CLOUD_PORT = 443;
  24. // Browsers that need chunking:
  25. static readonly chunkedBrowsers = { Chrome: 1 };
  26. static readonly 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.
  27. // Logging logic
  28. static readonly debug = false;
  29. static logLevel = DebugLevel.Disabled;
  30. static setLogLevel(level: string): void {
  31. const debugLevel = parseInt(level, 10);
  32. if (!isNaN(parseInt(level, 10))) {
  33. util.logLevel = debugLevel;
  34. } else {
  35. // If they are using truthy/falsy values for debug
  36. util.logLevel = level ? DebugLevel.All : DebugLevel.Disabled;
  37. }
  38. util.log = util.warn = util.error = util.noop;
  39. if (util.logLevel > DebugLevel.Disabled) {
  40. util.error = util._printWith("ERROR");
  41. }
  42. if (util.logLevel > DebugLevel.Errors) {
  43. util.warn = util._printWith("WARNING");
  44. }
  45. if (util.logLevel > DebugLevel.Warnings) {
  46. util.log = util._print;
  47. }
  48. }
  49. static setLogFunction(fn): void {
  50. if (fn.constructor !== Function) {
  51. util.warn(
  52. "The log function you passed in is not a function. Defaulting to regular logs."
  53. );
  54. } else {
  55. util._print = fn;
  56. }
  57. }
  58. private static _printWith(prefix) {
  59. return function () {
  60. const copy = Array.prototype.slice.call(arguments);
  61. copy.unshift(prefix);
  62. util._print.apply(util, copy);
  63. };
  64. }
  65. private static _print(...rest): void {
  66. let err = false;
  67. const copy = [...rest];
  68. copy.unshift("PeerJS: ");
  69. for (let i in copy) {
  70. if (copy[i] instanceof Error) {
  71. copy[i] = "(" + copy[i].name + ") " + copy[i].message;
  72. err = true;
  73. }
  74. }
  75. err ? console.error.apply(console, copy) : console.log.apply(console, copy);
  76. }
  77. // Returns browser-agnostic default config
  78. static readonly defaultConfig = DEFAULT_CONFIG;
  79. // Returns the current browser.
  80. static readonly browser: string = (function (global) {
  81. // @ts-ignore
  82. if (global.mozRTCPeerConnection) {
  83. return "Firefox";
  84. }
  85. // @ts-ignore
  86. if (global.webkitRTCPeerConnection) {
  87. return "Chrome";
  88. }
  89. if (global.RTCPeerConnection) {
  90. return "Supported";
  91. }
  92. return "Unsupported";
  93. })(window);
  94. // Lists which features are supported
  95. static readonly supports = (function () {
  96. if (typeof RTCPeerConnection === "undefined") {
  97. return {};
  98. }
  99. let data = true;
  100. let audioVideo = true;
  101. let binaryBlob = false;
  102. let sctp = false;
  103. // @ts-ignore
  104. const onnegotiationneeded = !!window.webkitRTCPeerConnection;
  105. let pc, dc;
  106. try {
  107. pc = new RTCPeerConnection(DEFAULT_CONFIG, {
  108. optional: [{ RtpDataChannels: true }]
  109. });
  110. } catch (e) {
  111. data = false;
  112. audioVideo = false;
  113. }
  114. if (data) {
  115. try {
  116. dc = pc.createDataChannel("_PEERJSTEST");
  117. } catch (e) {
  118. data = false;
  119. }
  120. }
  121. if (data) {
  122. // Binary test
  123. try {
  124. dc.binaryType = "blob";
  125. binaryBlob = true;
  126. } catch (e) { }
  127. // Reliable test.
  128. // Unfortunately Chrome is a bit unreliable about whether or not they
  129. // support reliable.
  130. const reliablePC = new RTCPeerConnection(DEFAULT_CONFIG, {});
  131. try {
  132. const reliableDC = reliablePC.createDataChannel(
  133. "_PEERJSRELIABLETEST",
  134. {}
  135. );
  136. sctp = reliableDC.reliable;
  137. } catch (e) { }
  138. reliablePC.close();
  139. }
  140. // FIXME: not really the best check...
  141. if (audioVideo) {
  142. audioVideo = !!pc.addStream;
  143. }
  144. // FIXME: this is not great because in theory it doesn't work for
  145. // av-only browsers (?).
  146. /*
  147. if (!onnegotiationneeded && data) {
  148. // sync default check.
  149. var negotiationPC = new RTCPeerConnection(defaultConfig, {optional: [{RtpDataChannels: true}]});
  150. negotiationPC.onnegotiationneeded = function() {
  151. onnegotiationneeded = true;
  152. // async check.
  153. if (util && util.supports) {
  154. util.supports.onnegotiationneeded = true;
  155. }
  156. };
  157. negotiationPC.createDataChannel('_PEERJSNEGOTIATIONTEST');
  158. setTimeout(function() {
  159. negotiationPC.close();
  160. }, 1000);
  161. }
  162. */
  163. if (pc) {
  164. pc.close();
  165. }
  166. return {
  167. audioVideo: audioVideo,
  168. data: data,
  169. binaryBlob: binaryBlob,
  170. binary: sctp, // deprecated; sctp implies binary support.
  171. reliable: sctp, // deprecated; sctp implies reliable data.
  172. sctp: sctp,
  173. onnegotiationneeded: onnegotiationneeded
  174. };
  175. })();
  176. // Ensure alphanumeric ids
  177. static validateId(id: string): boolean {
  178. // Allow empty ids
  179. return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(id);
  180. }
  181. static pack = BinaryPack.pack;
  182. static unpack = BinaryPack.unpack;
  183. static log(...rest): void {
  184. if (!util.debug) return;
  185. let err = false;
  186. const copy = [...rest];
  187. copy.unshift("PeerJS: ");
  188. for (let i in copy) {
  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. static warn(..._): void { }
  197. static error(..._): void { }
  198. // Binary stuff
  199. private static _dataCount = 1;
  200. // chunks a blob.
  201. static chunk(bl: Blob): any[] {
  202. const chunks = [];
  203. const size = bl.size;
  204. const total = Math.ceil(size / util.chunkedMTU);
  205. let index;
  206. let start = (index = 0);
  207. while (start < size) {
  208. const end = Math.min(size, start + util.chunkedMTU);
  209. const b = bl.slice(start, end);
  210. const chunk = {
  211. __peerData: this._dataCount,
  212. n: index,
  213. data: b,
  214. total: total
  215. };
  216. chunks.push(chunk);
  217. start = end;
  218. index++;
  219. }
  220. this._dataCount++;
  221. return chunks;
  222. }
  223. static blobToArrayBuffer(blob: Blob, cb: (arg: any) => void): void {
  224. const fr = new FileReader();
  225. fr.onload = function (evt) {
  226. // @ts-ignore
  227. cb(evt.target.result);
  228. };
  229. fr.readAsArrayBuffer(blob);
  230. }
  231. static blobToBinaryString(blob: Blob, cb: (arg: any) => void): void {
  232. const fr = new FileReader();
  233. fr.onload = function (evt) {
  234. // @ts-ignore
  235. cb(evt.target.result);
  236. };
  237. fr.readAsBinaryString(blob);
  238. }
  239. static binaryStringToArrayBuffer(binary): ArrayBuffer | SharedArrayBuffer {
  240. let byteArray = new Uint8Array(binary.length);
  241. for (let i = 0; i < binary.length; i++) {
  242. byteArray[i] = binary.charCodeAt(i) & 0xff;
  243. }
  244. return byteArray.buffer;
  245. }
  246. static randomToken(): string {
  247. return Math.random()
  248. .toString(36)
  249. .substr(2);
  250. }
  251. static isSecure(): boolean {
  252. return location.protocol === "https:";
  253. }
  254. }