peer.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /**
  2. * A peer who can initiate connections with other peers.
  3. */
  4. function Peer(id, options) {
  5. if (!(this instanceof Peer)) return new Peer(id, options);
  6. EventEmitter.call(this);
  7. // Deal with overloading
  8. if (id && id.constructor == Object) {
  9. options = id;
  10. id = undefined;
  11. } else {
  12. // Ensure id is a string
  13. id = id.toString();
  14. }
  15. //
  16. // Configurize options
  17. options = util.extend({
  18. debug: 0, // 1: Errors, 2: Warnings, 3: All logs
  19. host: '0.peerjs.com',
  20. port: 9000,
  21. key: 'peerjs',
  22. config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] }
  23. }, options);
  24. this.options = options;
  25. // Detect relative URL host.
  26. if (options.host === '/') {
  27. options.host = window.location.hostname;
  28. }
  29. // Set whether we use SSL to same as current host
  30. if (options.secure === undefined) {
  31. options.secure = util.isSecure();
  32. }
  33. // TODO: document this feature
  34. // Set a custom log function if present
  35. if (options.logFunction) {
  36. util.setLogFunction(options.logFunction):
  37. }
  38. util.setLogLevel(options.debug);
  39. //
  40. // Sanity checks
  41. // Ensure WebRTC supported
  42. if (!util.supports.audioVideo && !util.supports.data ) {
  43. this._delayedAbort('browser-incompatible', 'The current browser does not support WebRTC');
  44. return;
  45. }
  46. // Ensure alphanumeric id
  47. if (!util.validateId(id)) {
  48. this._delayedAbort('invalid-id', 'ID "' + id + '" is invalid');
  49. return;
  50. }
  51. // Ensure valid key
  52. if (!util.validateKey(options.key)) {
  53. this._delayedAbort('invalid-key', 'API KEY "' + options.key + '" is invalid');
  54. return;
  55. }
  56. // Ensure not using unsecure cloud server on SSL page
  57. if (options.secure && options.host === '0.peerjs.com') {
  58. this._delayedAbort('ssl-unavailable',
  59. 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.');
  60. return;
  61. }
  62. //
  63. // States.
  64. this.destroyed = false; // Connections have been killed
  65. this.disconnected = false; // Connection to PeerServer killed but P2P connections still active
  66. //
  67. // References
  68. this.connections = {}; // DataConnections for this peer.
  69. this.calls = {}; // MediaConnections for this peer
  70. //
  71. // Internal references
  72. this._managers = {}; // Managers for peer connections
  73. this._queued = []; // Queued connections to make.
  74. //
  75. // Start the connections
  76. if (id) {
  77. this._initialize(id);
  78. } else {
  79. this._retrieveId();
  80. }
  81. //
  82. };
  83. util.inherits(Peer, EventEmitter);
  84. Peer.prototype._retrieveId = function(cb) {
  85. var self = this;
  86. var http = new XMLHttpRequest();
  87. var protocol = this.options.secure ? 'https://' : 'http://';
  88. var url = protocol + this.options.host + ':' + this.options.port + '/' + this.options.key + '/id';
  89. var queryString = '?ts=' + new Date().getTime() + '' + Math.random();
  90. url += queryString;
  91. // If there's no ID we need to wait for one before trying to init socket.
  92. http.open('get', url, true);
  93. http.onerror = function(e) {
  94. util.error('Error retrieving ID', e);
  95. self._abort('server-error', 'Could not get an ID from the server');
  96. }
  97. http.onreadystatechange = function() {
  98. if (http.readyState !== 4) {
  99. return;
  100. }
  101. if (http.status !== 200) {
  102. http.onerror();
  103. return;
  104. }
  105. self._initialize(http.responseText);
  106. };
  107. http.send(null);
  108. };
  109. Peer.prototype._initialize = function(id) {
  110. var self = this;
  111. this.id = id;
  112. this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.key, this.id);
  113. this.socket.on('message', function(data) {
  114. self._dispatchMessage(data);
  115. });
  116. this.socket.on('error', function(error) {
  117. self._abort('socket-error', error);
  118. });
  119. this.socket.on('close', function() {
  120. // TODO: What if we disconnected on purpose?
  121. self._abort('socket-closed', 'Underlying socket has closed');
  122. });
  123. this.socket.start();
  124. }
  125. Peer.prototype._dispatchMessage = function(message) {
  126. var type = message.type;
  127. // Message types that don't involve a peer
  128. switch (type) {
  129. case 'OPEN':
  130. this._processQueue();
  131. this.emit('open', this.id);
  132. return;
  133. case 'ERROR':
  134. this._abort('server-error', payload.msg);
  135. return;
  136. case 'ID-TAKEN':
  137. this._abort('unavailable-id', 'ID `'+this.id+'` is taken');
  138. return;
  139. case 'INVALID-KEY':
  140. this._abort('invalid-key', 'API KEY "' + this._key + '" is invalid');
  141. return;
  142. }
  143. var peer = message.src;
  144. var managerId = message.manager;
  145. var manager = this._getManager(peer, managerId);
  146. var payload = message.payload;
  147. switch (message.type) {
  148. case 'OFFER':
  149. var options = {
  150. sdp: payload.sdp,
  151. labels: payload.labels,
  152. config: this.options.config
  153. };
  154. // Either forward to or create new manager
  155. if (!manager) {
  156. manager = this._createManager(managerId, peer, options);
  157. }
  158. manager.handleUpdate(options.labels);
  159. manager.handleSDP(payload.sdp, message.type, payload.call);
  160. break;
  161. case 'EXPIRE':
  162. peer.emit('error', new Error('Could not connect to peer ' + manager.peer));
  163. break;
  164. case 'ANSWER':
  165. // Forward to specific manager
  166. if (manager) {
  167. manager.handleSDP(payload.sdp, message.type);
  168. }
  169. break;
  170. case 'CANDIDATE':
  171. // Forward to specific manager
  172. if (manager) {
  173. manager.handleCandidate(payload);
  174. }
  175. break;
  176. case 'LEAVE':
  177. // Leave on all managers for a user
  178. if (this._managers[peer]) {
  179. var ids = Object.keys(this._managers[peer].managers);
  180. for (var i = 0; i < ids.length; i++) {
  181. this._managers[peer].managers[ids[i]].handleLeave();
  182. }
  183. }
  184. break;
  185. default:
  186. util.warn('Unrecognized message type:', message.type);
  187. break;
  188. }
  189. };
  190. /** Process queued calls to connect. */
  191. Peer.prototype._processQueue = function() {
  192. while (this._queued.length > 0) {
  193. var manager = this._queued.pop();
  194. manager.initialize(this.id, this.socket);
  195. }
  196. };
  197. /** Listeners for manager. */
  198. Peer.prototype._attachManagerListeners = function(manager) {
  199. var self = this;
  200. // Handle receiving a connection.
  201. manager.on('connection', function(connection) {
  202. self._managers[manager.peer].dataManager = manager;
  203. self.connections[connection.label] = connection;
  204. self.emit('connection', connection);
  205. });
  206. // Handle receiving a call
  207. manager.on('call', function(call) {
  208. self.calls[call.label] = call;
  209. self.emit('call', call);
  210. });
  211. // Handle a connection closing.
  212. manager.on('close', function() {
  213. if (!!self._managers[manager.peer]) {
  214. delete self._managers[manager.peer];
  215. // TODO: delete relevant calls and connections
  216. }
  217. });
  218. manager.on('error', function(err) {
  219. self.emit('error', err);
  220. });
  221. };
  222. Peer.prototype._getManager = function(peer, managerId) {
  223. if (this._managers[peer]) {
  224. return this._managers[peer].managers[managerId];
  225. }
  226. }
  227. Peer.prototype._getDataManager = function(peer) {
  228. if (this._managers[peer]) {
  229. return this._managers[peer].dataManager;
  230. }
  231. }
  232. /** Exposed connect function for users. Will try to connect later if user
  233. * is waiting for an ID. */
  234. Peer.prototype._createManager = function(managerId, peer, options) {
  235. if (this.disconnected) {
  236. var err = new Error('This Peer has been disconnected from the server and can no longer make connections.');
  237. err.type = 'server-disconnected';
  238. this.emit('error', err);
  239. return;
  240. }
  241. options = util.extend({
  242. config: this.options.config
  243. }, options);
  244. if (!this._managers[peer]) {
  245. this._managers[peer] = {nextId: 0, managers: {}};
  246. }
  247. managerId = managerId || peer + this._managers[peer].nextId++;
  248. var manager = new ConnectionManager(managerId, peer, options);
  249. if (!!this.id && !!this.socket) {
  250. manager.initialize(this.id, this.socket);
  251. } else {
  252. this._queued.push(manager);
  253. }
  254. this._attachManagerListeners(manager);
  255. this._managers[peer].managers[manager._managerId] = manager;
  256. return manager;
  257. };
  258. Peer.prototype.connect = function(peer, options) {
  259. var manager = this._getDataManager(peer);
  260. if (!manager) {
  261. manager = this._createManager(false, peer, options);
  262. }
  263. var connection = manager.connect(options);
  264. return connection;
  265. }
  266. Peer.prototype.call = function(peer, stream, options) {
  267. var manager = this._createManager(false, peer, options);
  268. var connection = manager.call(stream, options);
  269. return connection;
  270. }
  271. Peer.prototype._delayedAbort = function(type, message) {
  272. var self = this;
  273. util.setZeroTimeout(function(){
  274. self._abort(type, message);
  275. });
  276. }
  277. /** Destroys the Peer and emits an error message. */
  278. Peer.prototype._abort = function(type, message) {
  279. util.error('Aborting. Error:', message);
  280. var err = new Error(message);
  281. err.type = type;
  282. this.destroy();
  283. this.emit('error', err);
  284. };
  285. /**
  286. * Destroys the Peer: closes all active connections as well as the connection
  287. * to the server.
  288. * Warning: The peer can no longer create or accept connections after being
  289. * destroyed.
  290. */
  291. Peer.prototype.destroy = function() {
  292. if (!this.destroyed) {
  293. this._cleanup();
  294. this.destroyed = true;
  295. }
  296. };
  297. // TODO: UPDATE
  298. Peer.prototype._cleanup = function() {
  299. var self = this;
  300. if (!!this._managers) {
  301. var peers = Object.keys(this._managers);
  302. for (var i = 0, ii = peers.length; i < ii; i++) {
  303. var ids = Object.keys(this._managers[peers[i]]);
  304. for (var j = 0, jj = peers.length; j < jj; j++) {
  305. this._managers[peers[i]][ids[j]].close();
  306. }
  307. }
  308. }
  309. util.setZeroTimeout(function(){
  310. self.disconnect();
  311. });
  312. this.emit('close');
  313. };
  314. /**
  315. * Disconnects the Peer's connection to the PeerServer. Does not close any
  316. * active connections.
  317. * Warning: The peer can no longer create or accept connections after being
  318. * disconnected. It also cannot reconnect to the server.
  319. */
  320. Peer.prototype.disconnect = function() {
  321. if (!this.disconnected) {
  322. if (!!this.socket) {
  323. this.socket.close();
  324. }
  325. this.id = null;
  326. this.disconnected = true;
  327. }
  328. };
  329. /** The current browser. */
  330. Peer.browser = util.browserisms;
  331. exports.Peer = Peer;