util.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import * as BinaryPack from "js-binarypack";
  2. import { RTCPeerConnection } from "./adapter";
  3. const DEFAULT_CONFIG = {
  4. iceServers: [
  5. { urls: "stun:stun.l.google.com:19302" },
  6. { urls: "turn:0.peerjs.com:3478", username: "peerjs", credential: "peerjsp" }
  7. ],
  8. sdpSemantics: "unified-plan"
  9. };
  10. export class util {
  11. static noop(): void { }
  12. static readonly CLOUD_HOST = "0.peerjs.com";
  13. static readonly CLOUD_PORT = 443;
  14. // Browsers that need chunking:
  15. static readonly chunkedBrowsers = { Chrome: 1 };
  16. 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.
  17. // Returns browser-agnostic default config
  18. static readonly defaultConfig = DEFAULT_CONFIG;
  19. // Returns the current browser.
  20. static readonly browser: string = (function (global) {
  21. // @ts-ignore
  22. if (global.mozRTCPeerConnection) {
  23. return "Firefox";
  24. }
  25. // @ts-ignore
  26. if (global.webkitRTCPeerConnection) {
  27. return "Chrome";
  28. }
  29. if (global.RTCPeerConnection) {
  30. return "Supported";
  31. }
  32. return "Unsupported";
  33. })(window);
  34. // Lists which features are supported
  35. static readonly supports = (function () {
  36. if (typeof RTCPeerConnection === "undefined") {
  37. return {};
  38. }
  39. let data = true;
  40. let audioVideo = true;
  41. let binaryBlob = false;
  42. let sctp = false;
  43. // @ts-ignore
  44. const onnegotiationneeded = !!window.webkitRTCPeerConnection;
  45. let pc, dc;
  46. try {
  47. pc = new RTCPeerConnection(DEFAULT_CONFIG, {
  48. optional: [{ RtpDataChannels: true }]
  49. });
  50. } catch (e) {
  51. data = false;
  52. audioVideo = false;
  53. }
  54. if (data) {
  55. try {
  56. dc = pc.createDataChannel("_PEERJSTEST");
  57. } catch (e) {
  58. data = false;
  59. }
  60. }
  61. if (data) {
  62. // Binary test
  63. try {
  64. dc.binaryType = "blob";
  65. binaryBlob = true;
  66. } catch (e) { }
  67. // Reliable test.
  68. // Unfortunately Chrome is a bit unreliable about whether or not they
  69. // support reliable.
  70. const reliablePC = new RTCPeerConnection(DEFAULT_CONFIG, {});
  71. try {
  72. const reliableDC = reliablePC.createDataChannel(
  73. "_PEERJSRELIABLETEST",
  74. {}
  75. );
  76. sctp = reliableDC.ordered;
  77. } catch (e) { }
  78. reliablePC.close();
  79. }
  80. // FIXME: not really the best check...
  81. if (audioVideo) {
  82. audioVideo = !!pc.addStream;
  83. }
  84. if (pc) {
  85. pc.close();
  86. }
  87. return {
  88. audioVideo: audioVideo,
  89. data: data,
  90. binaryBlob: binaryBlob,
  91. binary: sctp, // deprecated; sctp implies binary support.
  92. reliable: sctp, // deprecated; sctp implies reliable data.
  93. sctp: sctp,
  94. onnegotiationneeded: onnegotiationneeded
  95. };
  96. })();
  97. // Ensure alphanumeric ids
  98. static validateId(id: string): boolean {
  99. // Allow empty ids
  100. return !id || /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(id);
  101. }
  102. static pack = BinaryPack.pack;
  103. static unpack = BinaryPack.unpack;
  104. // Binary stuff
  105. private static _dataCount = 1;
  106. // chunks a blob.
  107. static chunk(bl: Blob): any[] {
  108. const chunks = [];
  109. const size = bl.size;
  110. const total = Math.ceil(size / util.chunkedMTU);
  111. let index;
  112. let start = (index = 0);
  113. while (start < size) {
  114. const end = Math.min(size, start + util.chunkedMTU);
  115. const b = bl.slice(start, end);
  116. const chunk = {
  117. __peerData: this._dataCount,
  118. n: index,
  119. data: b,
  120. total: total
  121. };
  122. chunks.push(chunk);
  123. start = end;
  124. index++;
  125. }
  126. this._dataCount++;
  127. return chunks;
  128. }
  129. static blobToArrayBuffer(blob: Blob, cb: (arg: any) => void): void {
  130. const fr = new FileReader();
  131. fr.onload = function (evt) {
  132. // @ts-ignore
  133. cb(evt.target.result);
  134. };
  135. fr.readAsArrayBuffer(blob);
  136. }
  137. static blobToBinaryString(blob: Blob, cb: (arg: any) => void): void {
  138. const fr = new FileReader();
  139. fr.onload = function (evt) {
  140. // @ts-ignore
  141. cb(evt.target.result);
  142. };
  143. fr.readAsBinaryString(blob);
  144. }
  145. static binaryStringToArrayBuffer(binary): ArrayBuffer | SharedArrayBuffer {
  146. let byteArray = new Uint8Array(binary.length);
  147. for (let i = 0; i < binary.length; i++) {
  148. byteArray[i] = binary.charCodeAt(i) & 0xff;
  149. }
  150. return byteArray.buffer;
  151. }
  152. static randomToken(): string {
  153. return Math.random()
  154. .toString(36)
  155. .substr(2);
  156. }
  157. static isSecure(): boolean {
  158. return location.protocol === "https:";
  159. }
  160. }