negotiator.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import * as Reliable from "reliable";
  2. import { util } from "./util";
  3. import logger from "./logger";
  4. import {
  5. RTCPeerConnection,
  6. RTCSessionDescription,
  7. RTCIceCandidate
  8. } from "./adapter";
  9. import { MediaConnection } from "./mediaconnection";
  10. import { DataConnection } from "./dataconnection";
  11. import { ConnectionType, PeerErrorType, ConnectionEventType, ServerMessageType } from "./enums";
  12. import { BaseConnection } from "./baseconnection";
  13. /**
  14. * Manages all negotiations between Peers.
  15. */
  16. export class Negotiator {
  17. constructor(readonly connection: BaseConnection) { }
  18. /** Returns a PeerConnection object set up correctly (for data, media). */
  19. startConnection(options: any) {
  20. const peerConnection = this._startPeerConnection();
  21. // Set the connection's PC.
  22. this.connection.peerConnection = peerConnection;
  23. if (this.connection.type === ConnectionType.Media && options._stream) {
  24. this._addTracksToConnection(options._stream, peerConnection);
  25. }
  26. // What do we need to do now?
  27. if (options.originator) {
  28. if (this.connection.type === ConnectionType.Data) {
  29. const dataConnection = <DataConnection>this.connection;
  30. let config = {};
  31. if (!util.supports.sctp) {
  32. config = { reliable: options.reliable };
  33. }
  34. const dataChannel = peerConnection.createDataChannel(
  35. dataConnection.label,
  36. config
  37. );
  38. dataConnection.initialize(dataChannel);
  39. }
  40. this._makeOffer();
  41. } else {
  42. this.handleSDP("OFFER", options.sdp);
  43. }
  44. }
  45. /** Start a PC. */
  46. private _startPeerConnection(): RTCPeerConnection {
  47. logger.log("Creating RTCPeerConnection.");
  48. let optional = {};
  49. if (this.connection.type === ConnectionType.Data && !util.supports.sctp) {
  50. optional = { optional: [{ RtpDataChannels: true }] };
  51. } else if (this.connection.type === ConnectionType.Media) {
  52. // Interop req for chrome.
  53. optional = { optional: [{ DtlsSrtpKeyAgreement: true }] };
  54. }
  55. const peerConnection = new RTCPeerConnection(
  56. this.connection.provider.options.config,
  57. optional
  58. );
  59. this._setupListeners(peerConnection);
  60. return peerConnection;
  61. }
  62. /** Set up various WebRTC listeners. */
  63. private _setupListeners(
  64. peerConnection: RTCPeerConnection
  65. ) {
  66. const peerId = this.connection.peer;
  67. const connectionId = this.connection.connectionId;
  68. const connectionType = this.connection.type;
  69. const provider = this.connection.provider;
  70. // ICE CANDIDATES.
  71. logger.log("Listening for ICE candidates.");
  72. peerConnection.onicecandidate = (evt) => {
  73. if (evt.candidate) {
  74. logger.log("Received ICE candidates for:", peerId);
  75. provider.socket.send({
  76. type: ServerMessageType.Candidate,
  77. payload: {
  78. candidate: evt.candidate,
  79. type: connectionType,
  80. connectionId: connectionId
  81. },
  82. dst: peerId
  83. });
  84. }
  85. };
  86. peerConnection.oniceconnectionstatechange = () => {
  87. switch (peerConnection.iceConnectionState) {
  88. case "failed":
  89. logger.log(
  90. "iceConnectionState is failed, closing connections to " +
  91. peerId
  92. );
  93. this.connection.emit(
  94. ConnectionEventType.Error,
  95. new Error("Negotiation of connection to " + peerId + " failed.")
  96. );
  97. this.connection.close();
  98. break;
  99. case "closed":
  100. logger.log(
  101. "iceConnectionState is closed, closing connections to " +
  102. peerId
  103. );
  104. this.connection.emit(
  105. ConnectionEventType.Error,
  106. new Error("Negotiation of connection to " + peerId + " failed.")
  107. );
  108. this.connection.close();
  109. break;
  110. case "disconnected":
  111. logger.log(
  112. "iceConnectionState is disconnected, closing connections to " +
  113. peerId
  114. );
  115. break;
  116. case "completed":
  117. peerConnection.onicecandidate = util.noop;
  118. break;
  119. }
  120. this.connection.emit(ConnectionEventType.IceStateChanged, peerConnection.iceConnectionState);
  121. };
  122. // DATACONNECTION.
  123. logger.log("Listening for data channel");
  124. // Fired between offer and answer, so options should already be saved
  125. // in the options hash.
  126. peerConnection.ondatachannel = (evt) => {
  127. logger.log("Received data channel");
  128. const dataChannel = evt.channel;
  129. const connection = <DataConnection>(
  130. provider.getConnection(peerId, connectionId)
  131. );
  132. connection.initialize(dataChannel);
  133. };
  134. // MEDIACONNECTION.
  135. logger.log("Listening for remote stream");
  136. peerConnection.ontrack = (evt) => {
  137. logger.log("Received remote stream");
  138. const stream = evt.streams[0];
  139. const connection = provider.getConnection(peerId, connectionId);
  140. if (connection.type === ConnectionType.Media) {
  141. const mediaConnection = <MediaConnection>connection;
  142. this._addStreamToMediaConnection(stream, mediaConnection);
  143. }
  144. };
  145. }
  146. cleanup(): void {
  147. logger.log("Cleaning up PeerConnection to " + this.connection.peer);
  148. const peerConnection = this.connection.peerConnection;
  149. if (!peerConnection) {
  150. return;
  151. }
  152. this.connection.peerConnection = null;
  153. //unsubscribe from all PeerConnection's events
  154. peerConnection.onicecandidate = peerConnection.oniceconnectionstatechange = peerConnection.ondatachannel = peerConnection.ontrack = () => { };
  155. const peerConnectionNotClosed = peerConnection.signalingState !== "closed";
  156. let dataChannelNotClosed = false;
  157. if (this.connection.type === ConnectionType.Data) {
  158. const dataConnection = <DataConnection>this.connection;
  159. const dataChannel = dataConnection.dataChannel;
  160. if (dataChannel) {
  161. dataChannelNotClosed = !!dataChannel.readyState && dataChannel.readyState !== "closed";
  162. }
  163. }
  164. if (peerConnectionNotClosed || dataChannelNotClosed) {
  165. peerConnection.close();
  166. }
  167. }
  168. private async _makeOffer(): Promise<void> {
  169. const peerConnection = this.connection.peerConnection;
  170. const provider = this.connection.provider;
  171. try {
  172. const offer = await peerConnection.createOffer(
  173. this.connection.options.constraints
  174. );
  175. logger.log("Created offer.");
  176. if (!util.supports.sctp && this.connection.type === ConnectionType.Data) {
  177. const dataConnection = <DataConnection>this.connection;
  178. if (dataConnection.reliable) {
  179. offer.sdp = Reliable.higherBandwidthSDP(offer.sdp);
  180. }
  181. }
  182. if (this.connection.options.sdpTransform && typeof this.connection.options.sdpTransform === 'function') {
  183. offer.sdp = this.connection.options.sdpTransform(offer.sdp) || offer.sdp;
  184. }
  185. try {
  186. await peerConnection.setLocalDescription(offer);
  187. logger.log("Set localDescription:", offer, `for:${this.connection.peer}`);
  188. let payload: any = {
  189. sdp: offer,
  190. type: this.connection.type,
  191. connectionId: this.connection.connectionId,
  192. metadata: this.connection.metadata,
  193. browser: util.browser
  194. };
  195. if (this.connection.type === ConnectionType.Data) {
  196. const dataConnection = <DataConnection>this.connection;
  197. payload = {
  198. ...payload,
  199. label: dataConnection.label,
  200. reliable: dataConnection.reliable,
  201. serialization: dataConnection.serialization
  202. };
  203. }
  204. provider.socket.send({
  205. type: ServerMessageType.Offer,
  206. payload,
  207. dst: this.connection.peer
  208. });
  209. } catch (err) {
  210. // TODO: investigate why _makeOffer is being called from the answer
  211. if (
  212. err !=
  213. "OperationError: Failed to set local offer sdp: Called in wrong state: kHaveRemoteOffer"
  214. ) {
  215. provider.emitError(PeerErrorType.WebRTC, err);
  216. logger.log("Failed to setLocalDescription, ", err);
  217. }
  218. }
  219. } catch (err_1) {
  220. provider.emitError(PeerErrorType.WebRTC, err_1);
  221. logger.log("Failed to createOffer, ", err_1);
  222. }
  223. }
  224. private async _makeAnswer(): Promise<void> {
  225. const peerConnection = this.connection.peerConnection;
  226. const provider = this.connection.provider;
  227. try {
  228. const answer = await peerConnection.createAnswer();
  229. logger.log("Created answer.");
  230. if (!util.supports.sctp && this.connection.type === ConnectionType.Data) {
  231. const dataConnection = <DataConnection>this.connection;
  232. if (dataConnection.reliable) {
  233. answer.sdp = Reliable.higherBandwidthSDP(answer.sdp);
  234. }
  235. }
  236. if (this.connection.options.sdpTransform && typeof this.connection.options.sdpTransform === 'function') {
  237. answer.sdp = this.connection.options.sdpTransform(answer.sdp) || answer.sdp;
  238. }
  239. try {
  240. await peerConnection.setLocalDescription(answer);
  241. logger.log(`Set localDescription:`, answer, `for:${this.connection.peer}`);
  242. provider.socket.send({
  243. type: ServerMessageType.Answer,
  244. payload: {
  245. sdp: answer,
  246. type: this.connection.type,
  247. connectionId: this.connection.connectionId,
  248. browser: util.browser
  249. },
  250. dst: this.connection.peer
  251. });
  252. } catch (err) {
  253. provider.emitError(PeerErrorType.WebRTC, err);
  254. logger.log("Failed to setLocalDescription, ", err);
  255. }
  256. } catch (err_1) {
  257. provider.emitError(PeerErrorType.WebRTC, err_1);
  258. logger.log("Failed to create answer, ", err_1);
  259. }
  260. }
  261. /** Handle an SDP. */
  262. async handleSDP(
  263. type: string,
  264. sdp: any
  265. ): Promise<void> {
  266. sdp = new RTCSessionDescription(sdp);
  267. const peerConnection = this.connection.peerConnection;
  268. const provider = this.connection.provider;
  269. logger.log("Setting remote description", sdp);
  270. const self = this;
  271. try {
  272. await peerConnection.setRemoteDescription(sdp);
  273. logger.log(`Set remoteDescription:${type} for:${this.connection.peer}`);
  274. if (type === "OFFER") {
  275. await self._makeAnswer();
  276. }
  277. } catch (err) {
  278. provider.emitError(PeerErrorType.WebRTC, err);
  279. logger.log("Failed to setRemoteDescription, ", err);
  280. }
  281. }
  282. /** Handle a candidate. */
  283. async handleCandidate(ice: any): Promise<void> {
  284. const candidate = ice.candidate;
  285. const sdpMLineIndex = ice.sdpMLineIndex;
  286. const peerConnection = this.connection.peerConnection;
  287. const provider = this.connection.provider;
  288. try {
  289. await peerConnection.addIceCandidate(
  290. new RTCIceCandidate({
  291. sdpMLineIndex: sdpMLineIndex,
  292. candidate: candidate
  293. })
  294. );
  295. logger.log(`Added ICE candidate for:${this.connection.peer}`);
  296. } catch (err) {
  297. provider.emitError(PeerErrorType.WebRTC, err);
  298. logger.log("Failed to handleCandidate, ", err);
  299. }
  300. }
  301. private _addTracksToConnection(
  302. stream: MediaStream,
  303. peerConnection: RTCPeerConnection
  304. ): void {
  305. logger.log(`add tracks from stream ${stream.id} to peer connection`);
  306. if (!peerConnection.addTrack) {
  307. return logger.error(
  308. `Your browser does't support RTCPeerConnection#addTrack. Ignored.`
  309. );
  310. }
  311. stream.getTracks().forEach(track => {
  312. peerConnection.addTrack(track, stream);
  313. });
  314. }
  315. private _addStreamToMediaConnection(
  316. stream: MediaStream,
  317. mediaConnection: MediaConnection
  318. ): void {
  319. logger.log(
  320. `add stream ${stream.id} to media connection ${
  321. mediaConnection.connectionId
  322. }`
  323. );
  324. mediaConnection.addStream(stream);
  325. }
  326. }