common_shim.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /*
  2. * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
  3. *
  4. * Use of this source code is governed by a BSD-style license
  5. * that can be found in the LICENSE file in the root of the source
  6. * tree.
  7. */
  8. /* eslint-env node */
  9. 'use strict';
  10. Object.defineProperty(exports, "__esModule", {
  11. value: true
  12. });
  13. exports.removeExtmapAllowMixed = removeExtmapAllowMixed;
  14. exports.shimAddIceCandidateNullOrEmpty = shimAddIceCandidateNullOrEmpty;
  15. exports.shimConnectionState = shimConnectionState;
  16. exports.shimMaxMessageSize = shimMaxMessageSize;
  17. exports.shimParameterlessSetLocalDescription = shimParameterlessSetLocalDescription;
  18. exports.shimRTCIceCandidate = shimRTCIceCandidate;
  19. exports.shimRTCIceCandidateRelayProtocol = shimRTCIceCandidateRelayProtocol;
  20. exports.shimSendThrowTypeError = shimSendThrowTypeError;
  21. var _sdp = _interopRequireDefault(require("sdp"));
  22. var utils = _interopRequireWildcard(require("./utils"));
  23. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  24. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  25. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
  26. function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
  27. function shimRTCIceCandidate(window) {
  28. // foundation is arbitrarily chosen as an indicator for full support for
  29. // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
  30. if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) {
  31. return;
  32. }
  33. var NativeRTCIceCandidate = window.RTCIceCandidate;
  34. window.RTCIceCandidate = function RTCIceCandidate(args) {
  35. // Remove the a= which shouldn't be part of the candidate string.
  36. if (_typeof(args) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) {
  37. args = JSON.parse(JSON.stringify(args));
  38. args.candidate = args.candidate.substring(2);
  39. }
  40. if (args.candidate && args.candidate.length) {
  41. // Augment the native candidate with the parsed fields.
  42. var nativeCandidate = new NativeRTCIceCandidate(args);
  43. var parsedCandidate = _sdp["default"].parseCandidate(args.candidate);
  44. for (var key in parsedCandidate) {
  45. if (!(key in nativeCandidate)) {
  46. Object.defineProperty(nativeCandidate, key, {
  47. value: parsedCandidate[key]
  48. });
  49. }
  50. }
  51. // Override serializer to not serialize the extra attributes.
  52. nativeCandidate.toJSON = function toJSON() {
  53. return {
  54. candidate: nativeCandidate.candidate,
  55. sdpMid: nativeCandidate.sdpMid,
  56. sdpMLineIndex: nativeCandidate.sdpMLineIndex,
  57. usernameFragment: nativeCandidate.usernameFragment
  58. };
  59. };
  60. return nativeCandidate;
  61. }
  62. return new NativeRTCIceCandidate(args);
  63. };
  64. window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;
  65. // Hook up the augmented candidate in onicecandidate and
  66. // addEventListener('icecandidate', ...)
  67. utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) {
  68. if (e.candidate) {
  69. Object.defineProperty(e, 'candidate', {
  70. value: new window.RTCIceCandidate(e.candidate),
  71. writable: 'false'
  72. });
  73. }
  74. return e;
  75. });
  76. }
  77. function shimRTCIceCandidateRelayProtocol(window) {
  78. if (!window.RTCIceCandidate || window.RTCIceCandidate && 'relayProtocol' in window.RTCIceCandidate.prototype) {
  79. return;
  80. }
  81. // Hook up the augmented candidate in onicecandidate and
  82. // addEventListener('icecandidate', ...)
  83. utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) {
  84. if (e.candidate) {
  85. var parsedCandidate = _sdp["default"].parseCandidate(e.candidate.candidate);
  86. if (parsedCandidate.type === 'relay') {
  87. // This is a libwebrtc-specific mapping of local type preference
  88. // to relayProtocol.
  89. e.candidate.relayProtocol = {
  90. 0: 'tls',
  91. 1: 'tcp',
  92. 2: 'udp'
  93. }[parsedCandidate.priority >> 24];
  94. }
  95. }
  96. return e;
  97. });
  98. }
  99. function shimMaxMessageSize(window, browserDetails) {
  100. if (!window.RTCPeerConnection) {
  101. return;
  102. }
  103. if (!('sctp' in window.RTCPeerConnection.prototype)) {
  104. Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
  105. get: function get() {
  106. return typeof this._sctp === 'undefined' ? null : this._sctp;
  107. }
  108. });
  109. }
  110. var sctpInDescription = function sctpInDescription(description) {
  111. if (!description || !description.sdp) {
  112. return false;
  113. }
  114. var sections = _sdp["default"].splitSections(description.sdp);
  115. sections.shift();
  116. return sections.some(function (mediaSection) {
  117. var mLine = _sdp["default"].parseMLine(mediaSection);
  118. return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1;
  119. });
  120. };
  121. var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) {
  122. // TODO: Is there a better solution for detecting Firefox?
  123. var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
  124. if (match === null || match.length < 2) {
  125. return -1;
  126. }
  127. var version = parseInt(match[1], 10);
  128. // Test for NaN (yes, this is ugly)
  129. return version !== version ? -1 : version;
  130. };
  131. var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) {
  132. // Every implementation we know can send at least 64 KiB.
  133. // Note: Although Chrome is technically able to send up to 256 KiB, the
  134. // data does not reach the other peer reliably.
  135. // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
  136. var canSendMaxMessageSize = 65536;
  137. if (browserDetails.browser === 'firefox') {
  138. if (browserDetails.version < 57) {
  139. if (remoteIsFirefox === -1) {
  140. // FF < 57 will send in 16 KiB chunks using the deprecated PPID
  141. // fragmentation.
  142. canSendMaxMessageSize = 16384;
  143. } else {
  144. // However, other FF (and RAWRTC) can reassemble PPID-fragmented
  145. // messages. Thus, supporting ~2 GiB when sending.
  146. canSendMaxMessageSize = 2147483637;
  147. }
  148. } else if (browserDetails.version < 60) {
  149. // Currently, all FF >= 57 will reset the remote maximum message size
  150. // to the default value when a data channel is created at a later
  151. // stage. :(
  152. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
  153. canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536;
  154. } else {
  155. // FF >= 60 supports sending ~2 GiB
  156. canSendMaxMessageSize = 2147483637;
  157. }
  158. }
  159. return canSendMaxMessageSize;
  160. };
  161. var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) {
  162. // Note: 65536 bytes is the default value from the SDP spec. Also,
  163. // every implementation we know supports receiving 65536 bytes.
  164. var maxMessageSize = 65536;
  165. // FF 57 has a slightly incorrect default remote max message size, so
  166. // we need to adjust it here to avoid a failure when sending.
  167. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
  168. if (browserDetails.browser === 'firefox' && browserDetails.version === 57) {
  169. maxMessageSize = 65535;
  170. }
  171. var match = _sdp["default"].matchPrefix(description.sdp, 'a=max-message-size:');
  172. if (match.length > 0) {
  173. maxMessageSize = parseInt(match[0].substring(19), 10);
  174. } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) {
  175. // If the maximum message size is not present in the remote SDP and
  176. // both local and remote are Firefox, the remote peer can receive
  177. // ~2 GiB.
  178. maxMessageSize = 2147483637;
  179. }
  180. return maxMessageSize;
  181. };
  182. var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription;
  183. window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
  184. this._sctp = null;
  185. // Chrome decided to not expose .sctp in plan-b mode.
  186. // As usual, adapter.js has to do an 'ugly worakaround'
  187. // to cover up the mess.
  188. if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) {
  189. var _this$getConfiguratio = this.getConfiguration(),
  190. sdpSemantics = _this$getConfiguratio.sdpSemantics;
  191. if (sdpSemantics === 'plan-b') {
  192. Object.defineProperty(this, 'sctp', {
  193. get: function get() {
  194. return typeof this._sctp === 'undefined' ? null : this._sctp;
  195. },
  196. enumerable: true,
  197. configurable: true
  198. });
  199. }
  200. }
  201. if (sctpInDescription(arguments[0])) {
  202. // Check if the remote is FF.
  203. var isFirefox = getRemoteFirefoxVersion(arguments[0]);
  204. // Get the maximum message size the local peer is capable of sending
  205. var canSendMMS = getCanSendMaxMessageSize(isFirefox);
  206. // Get the maximum message size of the remote peer.
  207. var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);
  208. // Determine final maximum message size
  209. var maxMessageSize;
  210. if (canSendMMS === 0 && remoteMMS === 0) {
  211. maxMessageSize = Number.POSITIVE_INFINITY;
  212. } else if (canSendMMS === 0 || remoteMMS === 0) {
  213. maxMessageSize = Math.max(canSendMMS, remoteMMS);
  214. } else {
  215. maxMessageSize = Math.min(canSendMMS, remoteMMS);
  216. }
  217. // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
  218. // attribute.
  219. var sctp = {};
  220. Object.defineProperty(sctp, 'maxMessageSize', {
  221. get: function get() {
  222. return maxMessageSize;
  223. }
  224. });
  225. this._sctp = sctp;
  226. }
  227. return origSetRemoteDescription.apply(this, arguments);
  228. };
  229. }
  230. function shimSendThrowTypeError(window) {
  231. if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) {
  232. return;
  233. }
  234. // Note: Although Firefox >= 57 has a native implementation, the maximum
  235. // message size can be reset for all data channels at a later stage.
  236. // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
  237. function wrapDcSend(dc, pc) {
  238. var origDataChannelSend = dc.send;
  239. dc.send = function send() {
  240. var data = arguments[0];
  241. var length = data.length || data.size || data.byteLength;
  242. if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) {
  243. throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)');
  244. }
  245. return origDataChannelSend.apply(dc, arguments);
  246. };
  247. }
  248. var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel;
  249. window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() {
  250. var dataChannel = origCreateDataChannel.apply(this, arguments);
  251. wrapDcSend(dataChannel, this);
  252. return dataChannel;
  253. };
  254. utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) {
  255. wrapDcSend(e.channel, e.target);
  256. return e;
  257. });
  258. }
  259. /* shims RTCConnectionState by pretending it is the same as iceConnectionState.
  260. * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
  261. * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect
  262. * since DTLS failures would be hidden. See
  263. * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
  264. * for the Firefox tracking bug.
  265. */
  266. function shimConnectionState(window) {
  267. if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) {
  268. return;
  269. }
  270. var proto = window.RTCPeerConnection.prototype;
  271. Object.defineProperty(proto, 'connectionState', {
  272. get: function get() {
  273. return {
  274. completed: 'connected',
  275. checking: 'connecting'
  276. }[this.iceConnectionState] || this.iceConnectionState;
  277. },
  278. enumerable: true,
  279. configurable: true
  280. });
  281. Object.defineProperty(proto, 'onconnectionstatechange', {
  282. get: function get() {
  283. return this._onconnectionstatechange || null;
  284. },
  285. set: function set(cb) {
  286. if (this._onconnectionstatechange) {
  287. this.removeEventListener('connectionstatechange', this._onconnectionstatechange);
  288. delete this._onconnectionstatechange;
  289. }
  290. if (cb) {
  291. this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb);
  292. }
  293. },
  294. enumerable: true,
  295. configurable: true
  296. });
  297. ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) {
  298. var origMethod = proto[method];
  299. proto[method] = function () {
  300. if (!this._connectionstatechangepoly) {
  301. this._connectionstatechangepoly = function (e) {
  302. var pc = e.target;
  303. if (pc._lastConnectionState !== pc.connectionState) {
  304. pc._lastConnectionState = pc.connectionState;
  305. var newEvent = new Event('connectionstatechange', e);
  306. pc.dispatchEvent(newEvent);
  307. }
  308. return e;
  309. };
  310. this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly);
  311. }
  312. return origMethod.apply(this, arguments);
  313. };
  314. });
  315. }
  316. function removeExtmapAllowMixed(window, browserDetails) {
  317. /* remove a=extmap-allow-mixed for webrtc.org < M71 */
  318. if (!window.RTCPeerConnection) {
  319. return;
  320. }
  321. if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) {
  322. return;
  323. }
  324. if (browserDetails.browser === 'safari' && browserDetails.version >= 605) {
  325. return;
  326. }
  327. var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription;
  328. window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) {
  329. if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) {
  330. var sdp = desc.sdp.split('\n').filter(function (line) {
  331. return line.trim() !== 'a=extmap-allow-mixed';
  332. }).join('\n');
  333. // Safari enforces read-only-ness of RTCSessionDescription fields.
  334. if (window.RTCSessionDescription && desc instanceof window.RTCSessionDescription) {
  335. arguments[0] = new window.RTCSessionDescription({
  336. type: desc.type,
  337. sdp: sdp
  338. });
  339. } else {
  340. desc.sdp = sdp;
  341. }
  342. }
  343. return nativeSRD.apply(this, arguments);
  344. };
  345. }
  346. function shimAddIceCandidateNullOrEmpty(window, browserDetails) {
  347. // Support for addIceCandidate(null or undefined)
  348. // as well as addIceCandidate({candidate: "", ...})
  349. // https://bugs.chromium.org/p/chromium/issues/detail?id=978582
  350. // Note: must be called before other polyfills which change the signature.
  351. if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) {
  352. return;
  353. }
  354. var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate;
  355. if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) {
  356. return;
  357. }
  358. window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() {
  359. if (!arguments[0]) {
  360. if (arguments[1]) {
  361. arguments[1].apply(null);
  362. }
  363. return Promise.resolve();
  364. }
  365. // Firefox 68+ emits and processes {candidate: "", ...}, ignore
  366. // in older versions.
  367. // Native support for ignoring exists for Chrome M77+.
  368. // Safari ignores as well, exact version unknown but works in the same
  369. // version that also ignores addIceCandidate(null).
  370. if ((browserDetails.browser === 'chrome' && browserDetails.version < 78 || browserDetails.browser === 'firefox' && browserDetails.version < 68 || browserDetails.browser === 'safari') && arguments[0] && arguments[0].candidate === '') {
  371. return Promise.resolve();
  372. }
  373. return nativeAddIceCandidate.apply(this, arguments);
  374. };
  375. }
  376. // Note: Make sure to call this ahead of APIs that modify
  377. // setLocalDescription.length
  378. function shimParameterlessSetLocalDescription(window, browserDetails) {
  379. if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) {
  380. return;
  381. }
  382. var nativeSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription;
  383. if (!nativeSetLocalDescription || nativeSetLocalDescription.length === 0) {
  384. return;
  385. }
  386. window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() {
  387. var _this = this;
  388. var desc = arguments[0] || {};
  389. if (_typeof(desc) !== 'object' || desc.type && desc.sdp) {
  390. return nativeSetLocalDescription.apply(this, arguments);
  391. }
  392. // The remaining steps should technically happen when SLD comes off the
  393. // RTCPeerConnection's operations chain (not ahead of going on it), but
  394. // this is too difficult to shim. Instead, this shim only covers the
  395. // common case where the operations chain is empty. This is imperfect, but
  396. // should cover many cases. Rationale: Even if we can't reduce the glare
  397. // window to zero on imperfect implementations, there's value in tapping
  398. // into the perfect negotiation pattern that several browsers support.
  399. desc = {
  400. type: desc.type,
  401. sdp: desc.sdp
  402. };
  403. if (!desc.type) {
  404. switch (this.signalingState) {
  405. case 'stable':
  406. case 'have-local-offer':
  407. case 'have-remote-pranswer':
  408. desc.type = 'offer';
  409. break;
  410. default:
  411. desc.type = 'answer';
  412. break;
  413. }
  414. }
  415. if (desc.sdp || desc.type !== 'offer' && desc.type !== 'answer') {
  416. return nativeSetLocalDescription.apply(this, [desc]);
  417. }
  418. var func = desc.type === 'offer' ? this.createOffer : this.createAnswer;
  419. return func.apply(this).then(function (d) {
  420. return nativeSetLocalDescription.apply(_this, [d]);
  421. });
  422. };
  423. }