peer.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  1. import { EventEmitter } from "eventemitter3";
  2. import { util } from "./util";
  3. import logger, { LogLevel } from "./logger";
  4. import { Socket } from "./socket";
  5. import { MediaConnection } from "./mediaconnection";
  6. import type { DataConnection } from "./dataconnection/DataConnection";
  7. import {
  8. ConnectionType,
  9. PeerErrorType,
  10. SocketEventType,
  11. ServerMessageType,
  12. } from "./enums";
  13. import type { ServerMessage } from "./servermessage";
  14. import { API } from "./api";
  15. import type {
  16. PeerConnectOption,
  17. PeerJSOption,
  18. CallOption,
  19. } from "./optionInterfaces";
  20. import { BinaryPack } from "./dataconnection/BufferedConnection/BinaryPack";
  21. import { Raw } from "./dataconnection/BufferedConnection/Raw";
  22. import { Json } from "./dataconnection/BufferedConnection/Json";
  23. class PeerOptions implements PeerJSOption {
  24. /**
  25. * Prints log messages depending on the debug level passed in.
  26. */
  27. debug?: LogLevel;
  28. /**
  29. * Server host. Defaults to `0.peerjs.com`.
  30. * Also accepts `'/'` to signify relative hostname.
  31. */
  32. host?: string;
  33. /**
  34. * Server port. Defaults to `443`.
  35. */
  36. port?: number;
  37. /**
  38. * The path where your self-hosted PeerServer is running. Defaults to `'/'`
  39. */
  40. path?: string;
  41. /**
  42. * API key for the PeerServer.
  43. * This is not used anymore.
  44. * @deprecated
  45. */
  46. key?: string;
  47. token?: string;
  48. /**
  49. * Configuration hash passed to RTCPeerConnection.
  50. * This hash contains any custom ICE/TURN server configuration.
  51. *
  52. * Defaults to {@apilink util.defaultConfig}
  53. */
  54. config?: any;
  55. /**
  56. * Set to true `true` if you're using TLS.
  57. * :::danger
  58. * If possible *always use TLS*
  59. * :::
  60. */
  61. secure?: boolean;
  62. pingInterval?: number;
  63. referrerPolicy?: ReferrerPolicy;
  64. logFunction?: (logLevel: LogLevel, ...rest: any[]) => void;
  65. serializers?: SerializerMapping;
  66. }
  67. class PeerError extends Error {
  68. constructor(type: PeerErrorType, err: Error | string) {
  69. if (typeof err === "string") {
  70. super(err);
  71. } else {
  72. super();
  73. Object.assign(this, err);
  74. }
  75. this.type = type;
  76. }
  77. type: PeerErrorType;
  78. }
  79. export type { PeerError, PeerOptions };
  80. export type SerializerMapping = {
  81. [key: string]: new (
  82. peerId: string,
  83. provider: Peer,
  84. options: any,
  85. ) => DataConnection;
  86. };
  87. export type PeerEvents = {
  88. /**
  89. * Emitted when a connection to the PeerServer is established.
  90. *
  91. * You may use the peer before this is emitted, but messages to the server will be queued. <code>id</code> is the brokering ID of the peer (which was either provided in the constructor or assigned by the server).<span class='tip'>You should not wait for this event before connecting to other peers if connection speed is important.</span>
  92. */
  93. open: (id: string) => void;
  94. /**
  95. * Emitted when a new data connection is established from a remote peer.
  96. */
  97. connection: (dataConnection: DataConnection) => void;
  98. /**
  99. * Emitted when a remote peer attempts to call you.
  100. */
  101. call: (mediaConnection: MediaConnection) => void;
  102. /**
  103. * Emitted when the peer is destroyed and can no longer accept or create any new connections.
  104. */
  105. close: () => void;
  106. /**
  107. * Emitted when the peer is disconnected from the signalling server
  108. */
  109. disconnected: (currentId: string) => void;
  110. /**
  111. * Errors on the peer are almost always fatal and will destroy the peer.
  112. *
  113. * Errors from the underlying socket and PeerConnections are forwarded here.
  114. */
  115. error: (error: PeerError) => void;
  116. };
  117. /**
  118. * A peer who can initiate connections with other peers.
  119. */
  120. export class Peer extends EventEmitter<PeerEvents> {
  121. private static readonly DEFAULT_KEY = "peerjs";
  122. protected readonly _serializers: SerializerMapping = {
  123. raw: Raw,
  124. json: Json,
  125. binary: BinaryPack,
  126. "binary-utf8": BinaryPack,
  127. default: BinaryPack,
  128. };
  129. private readonly _options: PeerOptions;
  130. private readonly _api: API;
  131. private readonly _socket: Socket;
  132. private _id: string | null = null;
  133. private _lastServerId: string | null = null;
  134. // States.
  135. private _destroyed = false; // Connections have been killed
  136. private _disconnected = false; // Connection to PeerServer killed but P2P connections still active
  137. private _open = false; // Sockets and such are not yet open.
  138. private readonly _connections: Map<
  139. string,
  140. (DataConnection | MediaConnection)[]
  141. > = new Map(); // All connections for this peer.
  142. private readonly _lostMessages: Map<string, ServerMessage[]> = new Map(); // src => [list of messages]
  143. /**
  144. * The brokering ID of this peer
  145. *
  146. * If no ID was specified in {@apilink Peer | the constructor},
  147. * this will be `undefined` until the {@apilink PeerEvents | `open`} event is emitted.
  148. */
  149. get id() {
  150. return this._id;
  151. }
  152. get options() {
  153. return this._options;
  154. }
  155. get open() {
  156. return this._open;
  157. }
  158. /**
  159. * @internal
  160. */
  161. get socket() {
  162. return this._socket;
  163. }
  164. /**
  165. * A hash of all connections associated with this peer, keyed by the remote peer's ID.
  166. * @deprecated
  167. * Return type will change from Object to Map<string,[]>
  168. */
  169. get connections(): Object {
  170. const plainConnections = Object.create(null);
  171. for (const [k, v] of this._connections) {
  172. plainConnections[k] = v;
  173. }
  174. return plainConnections;
  175. }
  176. /**
  177. * true if this peer and all of its connections can no longer be used.
  178. */
  179. get destroyed() {
  180. return this._destroyed;
  181. }
  182. /**
  183. * false if there is an active connection to the PeerServer.
  184. */
  185. get disconnected() {
  186. return this._disconnected;
  187. }
  188. /**
  189. * A peer can connect to other peers and listen for connections.
  190. */
  191. constructor();
  192. /**
  193. * A peer can connect to other peers and listen for connections.
  194. * @param options for specifying details about PeerServer
  195. */
  196. constructor(options: PeerOptions);
  197. /**
  198. * A peer can connect to other peers and listen for connections.
  199. * @param id Other peers can connect to this peer using the provided ID.
  200. * If no ID is given, one will be generated by the brokering server.
  201. * The ID must start and end with an alphanumeric character (lower or upper case character or a digit). In the middle of the ID spaces, dashes (-) and underscores (_) are allowed. Use {@apilink PeerOptions.metadata } to send identifying information.
  202. * @param options for specifying details about PeerServer
  203. */
  204. constructor(id: string, options?: PeerOptions);
  205. constructor(id?: string | PeerOptions, options?: PeerOptions) {
  206. super();
  207. let userId: string | undefined;
  208. // Deal with overloading
  209. if (id && id.constructor == Object) {
  210. options = id as PeerOptions;
  211. } else if (id) {
  212. userId = id.toString();
  213. }
  214. // Configurize options
  215. options = {
  216. debug: 0, // 1: Errors, 2: Warnings, 3: All logs
  217. host: util.CLOUD_HOST,
  218. port: util.CLOUD_PORT,
  219. path: "/",
  220. key: Peer.DEFAULT_KEY,
  221. token: util.randomToken(),
  222. config: util.defaultConfig,
  223. referrerPolicy: "strict-origin-when-cross-origin",
  224. serializers: {},
  225. ...options,
  226. };
  227. this._options = options;
  228. this._serializers = { ...this._serializers, ...this.options.serializers };
  229. // Detect relative URL host.
  230. if (this._options.host === "/") {
  231. this._options.host = window.location.hostname;
  232. }
  233. // Set path correctly.
  234. if (this._options.path) {
  235. if (this._options.path[0] !== "/") {
  236. this._options.path = "/" + this._options.path;
  237. }
  238. if (this._options.path[this._options.path.length - 1] !== "/") {
  239. this._options.path += "/";
  240. }
  241. }
  242. // Set whether we use SSL to same as current host
  243. if (
  244. this._options.secure === undefined &&
  245. this._options.host !== util.CLOUD_HOST
  246. ) {
  247. this._options.secure = util.isSecure();
  248. } else if (this._options.host == util.CLOUD_HOST) {
  249. this._options.secure = true;
  250. }
  251. // Set a custom log function if present
  252. if (this._options.logFunction) {
  253. logger.setLogFunction(this._options.logFunction);
  254. }
  255. logger.logLevel = this._options.debug || 0;
  256. this._api = new API(options);
  257. this._socket = this._createServerConnection();
  258. // Sanity checks
  259. // Ensure WebRTC supported
  260. if (!util.supports.audioVideo && !util.supports.data) {
  261. this._delayedAbort(
  262. PeerErrorType.BrowserIncompatible,
  263. "The current browser does not support WebRTC",
  264. );
  265. return;
  266. }
  267. // Ensure alphanumeric id
  268. if (!!userId && !util.validateId(userId)) {
  269. this._delayedAbort(PeerErrorType.InvalidID, `ID "${userId}" is invalid`);
  270. return;
  271. }
  272. if (userId) {
  273. this._initialize(userId);
  274. } else {
  275. this._api
  276. .retrieveId()
  277. .then((id) => this._initialize(id))
  278. .catch((error) => this._abort(PeerErrorType.ServerError, error));
  279. }
  280. }
  281. private _createServerConnection(): Socket {
  282. const socket = new Socket(
  283. this._options.secure,
  284. this._options.host!,
  285. this._options.port!,
  286. this._options.path!,
  287. this._options.key!,
  288. this._options.pingInterval,
  289. );
  290. socket.on(SocketEventType.Message, (data: ServerMessage) => {
  291. this._handleMessage(data);
  292. });
  293. socket.on(SocketEventType.Error, (error: string) => {
  294. this._abort(PeerErrorType.SocketError, error);
  295. });
  296. socket.on(SocketEventType.Disconnected, () => {
  297. if (this.disconnected) {
  298. return;
  299. }
  300. this.emitError(PeerErrorType.Network, "Lost connection to server.");
  301. this.disconnect();
  302. });
  303. socket.on(SocketEventType.Close, () => {
  304. if (this.disconnected) {
  305. return;
  306. }
  307. this._abort(
  308. PeerErrorType.SocketClosed,
  309. "Underlying socket is already closed.",
  310. );
  311. });
  312. return socket;
  313. }
  314. /** Initialize a connection with the server. */
  315. private _initialize(id: string): void {
  316. this._id = id;
  317. this.socket.start(id, this._options.token!);
  318. }
  319. /** Handles messages from the server. */
  320. private _handleMessage(message: ServerMessage): void {
  321. const type = message.type;
  322. const payload = message.payload;
  323. const peerId = message.src;
  324. switch (type) {
  325. case ServerMessageType.Open: // The connection to the server is open.
  326. this._lastServerId = this.id;
  327. this._open = true;
  328. this.emit("open", this.id);
  329. break;
  330. case ServerMessageType.Error: // Server error.
  331. this._abort(PeerErrorType.ServerError, payload.msg);
  332. break;
  333. case ServerMessageType.IdTaken: // The selected ID is taken.
  334. this._abort(PeerErrorType.UnavailableID, `ID "${this.id}" is taken`);
  335. break;
  336. case ServerMessageType.InvalidKey: // The given API key cannot be found.
  337. this._abort(
  338. PeerErrorType.InvalidKey,
  339. `API KEY "${this._options.key}" is invalid`,
  340. );
  341. break;
  342. case ServerMessageType.Leave: // Another peer has closed its connection to this peer.
  343. logger.log(`Received leave message from ${peerId}`);
  344. this._cleanupPeer(peerId);
  345. this._connections.delete(peerId);
  346. break;
  347. case ServerMessageType.Expire: // The offer sent to a peer has expired without response.
  348. this.emitError(
  349. PeerErrorType.PeerUnavailable,
  350. `Could not connect to peer ${peerId}`,
  351. );
  352. break;
  353. case ServerMessageType.Offer: {
  354. // we should consider switching this to CALL/CONNECT, but this is the least breaking option.
  355. const connectionId = payload.connectionId;
  356. let connection = this.getConnection(peerId, connectionId);
  357. if (connection) {
  358. connection.close();
  359. logger.warn(
  360. `Offer received for existing Connection ID:${connectionId}`,
  361. );
  362. }
  363. // Create a new connection.
  364. if (payload.type === ConnectionType.Media) {
  365. const mediaConnection = new MediaConnection(peerId, this, {
  366. connectionId: connectionId,
  367. _payload: payload,
  368. metadata: payload.metadata,
  369. });
  370. connection = mediaConnection;
  371. this._addConnection(peerId, connection);
  372. this.emit("call", mediaConnection);
  373. } else if (payload.type === ConnectionType.Data) {
  374. const dataConnection = new this._serializers[payload.serialization](
  375. peerId,
  376. this,
  377. {
  378. connectionId: connectionId,
  379. _payload: payload,
  380. metadata: payload.metadata,
  381. label: payload.label,
  382. serialization: payload.serialization,
  383. reliable: payload.reliable,
  384. },
  385. );
  386. connection = dataConnection;
  387. this._addConnection(peerId, connection);
  388. this.emit("connection", dataConnection);
  389. } else {
  390. logger.warn(`Received malformed connection type:${payload.type}`);
  391. return;
  392. }
  393. // Find messages.
  394. const messages = this._getMessages(connectionId);
  395. for (const message of messages) {
  396. connection.handleMessage(message);
  397. }
  398. break;
  399. }
  400. default: {
  401. if (!payload) {
  402. logger.warn(
  403. `You received a malformed message from ${peerId} of type ${type}`,
  404. );
  405. return;
  406. }
  407. const connectionId = payload.connectionId;
  408. const connection = this.getConnection(peerId, connectionId);
  409. if (connection && connection.peerConnection) {
  410. // Pass it on.
  411. connection.handleMessage(message);
  412. } else if (connectionId) {
  413. // Store for possible later use
  414. this._storeMessage(connectionId, message);
  415. } else {
  416. logger.warn("You received an unrecognized message:", message);
  417. }
  418. break;
  419. }
  420. }
  421. }
  422. /** Stores messages without a set up connection, to be claimed later. */
  423. private _storeMessage(connectionId: string, message: ServerMessage): void {
  424. if (!this._lostMessages.has(connectionId)) {
  425. this._lostMessages.set(connectionId, []);
  426. }
  427. this._lostMessages.get(connectionId).push(message);
  428. }
  429. /**
  430. * Retrieve messages from lost message store
  431. * @internal
  432. */
  433. //TODO Change it to private
  434. public _getMessages(connectionId: string): ServerMessage[] {
  435. const messages = this._lostMessages.get(connectionId);
  436. if (messages) {
  437. this._lostMessages.delete(connectionId);
  438. return messages;
  439. }
  440. return [];
  441. }
  442. /**
  443. * Connects to the remote peer specified by id and returns a data connection.
  444. * @param peer The brokering ID of the remote peer (their {@apilink Peer.id}).
  445. * @param options for specifying details about Peer Connection
  446. */
  447. connect(peer: string, options: PeerConnectOption): DataConnection {
  448. options = {
  449. serialization: "default",
  450. ...options,
  451. };
  452. if (this.disconnected) {
  453. logger.warn(
  454. "You cannot connect to a new Peer because you called " +
  455. ".disconnect() on this Peer and ended your connection with the " +
  456. "server. You can create a new Peer to reconnect, or call reconnect " +
  457. "on this peer if you believe its ID to still be available.",
  458. );
  459. this.emitError(
  460. PeerErrorType.Disconnected,
  461. "Cannot connect to new Peer after disconnecting from server.",
  462. );
  463. return;
  464. }
  465. const dataConnection = new this._serializers[options.serialization](
  466. peer,
  467. this,
  468. options,
  469. );
  470. this._addConnection(peer, dataConnection);
  471. return dataConnection;
  472. }
  473. /**
  474. * Calls the remote peer specified by id and returns a media connection.
  475. * @param peer The brokering ID of the remote peer (their peer.id).
  476. * @param stream The caller's media stream
  477. * @param options Metadata associated with the connection, passed in by whoever initiated the connection.
  478. */
  479. call(
  480. peer: string,
  481. stream: MediaStream,
  482. options: CallOption = {},
  483. ): MediaConnection {
  484. if (this.disconnected) {
  485. logger.warn(
  486. "You cannot connect to a new Peer because you called " +
  487. ".disconnect() on this Peer and ended your connection with the " +
  488. "server. You can create a new Peer to reconnect.",
  489. );
  490. this.emitError(
  491. PeerErrorType.Disconnected,
  492. "Cannot connect to new Peer after disconnecting from server.",
  493. );
  494. return;
  495. }
  496. if (!stream) {
  497. logger.error(
  498. "To call a peer, you must provide a stream from your browser's `getUserMedia`.",
  499. );
  500. return;
  501. }
  502. const mediaConnection = new MediaConnection(peer, this, {
  503. ...options,
  504. _stream: stream,
  505. });
  506. this._addConnection(peer, mediaConnection);
  507. return mediaConnection;
  508. }
  509. /** Add a data/media connection to this peer. */
  510. private _addConnection(
  511. peerId: string,
  512. connection: MediaConnection | DataConnection,
  513. ): void {
  514. logger.log(
  515. `add connection ${connection.type}:${connection.connectionId} to peerId:${peerId}`,
  516. );
  517. if (!this._connections.has(peerId)) {
  518. this._connections.set(peerId, []);
  519. }
  520. this._connections.get(peerId).push(connection);
  521. }
  522. //TODO should be private
  523. _removeConnection(connection: DataConnection | MediaConnection): void {
  524. const connections = this._connections.get(connection.peer);
  525. if (connections) {
  526. const index = connections.indexOf(connection);
  527. if (index !== -1) {
  528. connections.splice(index, 1);
  529. }
  530. }
  531. //remove from lost messages
  532. this._lostMessages.delete(connection.connectionId);
  533. }
  534. /** Retrieve a data/media connection for this peer. */
  535. getConnection(
  536. peerId: string,
  537. connectionId: string,
  538. ): null | DataConnection | MediaConnection {
  539. const connections = this._connections.get(peerId);
  540. if (!connections) {
  541. return null;
  542. }
  543. for (const connection of connections) {
  544. if (connection.connectionId === connectionId) {
  545. return connection;
  546. }
  547. }
  548. return null;
  549. }
  550. private _delayedAbort(type: PeerErrorType, message: string | Error): void {
  551. setTimeout(() => {
  552. this._abort(type, message);
  553. }, 0);
  554. }
  555. /**
  556. * Emits an error message and destroys the Peer.
  557. * The Peer is not destroyed if it's in a disconnected state, in which case
  558. * it retains its disconnected state and its existing connections.
  559. */
  560. private _abort(type: PeerErrorType, message: string | Error): void {
  561. logger.error("Aborting!");
  562. this.emitError(type, message);
  563. if (!this._lastServerId) {
  564. this.destroy();
  565. } else {
  566. this.disconnect();
  567. }
  568. }
  569. /** Emits a typed error message. */
  570. emitError(type: PeerErrorType, err: string | Error): void {
  571. logger.error("Error:", err);
  572. this.emit("error", new PeerError(type, err));
  573. }
  574. /**
  575. * Destroys the Peer: closes all active connections as well as the connection
  576. * to the server.
  577. *
  578. * :::caution
  579. * This cannot be undone; the respective peer object will no longer be able
  580. * to create or receive any connections, its ID will be forfeited on the server,
  581. * and all of its data and media connections will be closed.
  582. * :::
  583. */
  584. destroy(): void {
  585. if (this.destroyed) {
  586. return;
  587. }
  588. logger.log(`Destroy peer with ID:${this.id}`);
  589. this.disconnect();
  590. this._cleanup();
  591. this._destroyed = true;
  592. this.emit("close");
  593. }
  594. /** Disconnects every connection on this peer. */
  595. private _cleanup(): void {
  596. for (const peerId of this._connections.keys()) {
  597. this._cleanupPeer(peerId);
  598. this._connections.delete(peerId);
  599. }
  600. this.socket.removeAllListeners();
  601. }
  602. /** Closes all connections to this peer. */
  603. private _cleanupPeer(peerId: string): void {
  604. const connections = this._connections.get(peerId);
  605. if (!connections) return;
  606. for (const connection of connections) {
  607. connection.close();
  608. }
  609. }
  610. /**
  611. * Disconnects the Peer's connection to the PeerServer. Does not close any
  612. * active connections.
  613. * Warning: The peer can no longer create or accept connections after being
  614. * disconnected. It also cannot reconnect to the server.
  615. */
  616. disconnect(): void {
  617. if (this.disconnected) {
  618. return;
  619. }
  620. const currentId = this.id;
  621. logger.log(`Disconnect peer with ID:${currentId}`);
  622. this._disconnected = true;
  623. this._open = false;
  624. this.socket.close();
  625. this._lastServerId = currentId;
  626. this._id = null;
  627. this.emit("disconnected", currentId);
  628. }
  629. /** Attempts to reconnect with the same ID.
  630. *
  631. * Only {@apilink Peer.disconnect | disconnected peers} can be reconnected.
  632. * Destroyed peers cannot be reconnected.
  633. * If the connection fails (as an example, if the peer's old ID is now taken),
  634. * the peer's existing connections will not close, but any associated errors events will fire.
  635. */
  636. reconnect(): void {
  637. if (this.disconnected && !this.destroyed) {
  638. logger.log(
  639. `Attempting reconnection to server with ID ${this._lastServerId}`,
  640. );
  641. this._disconnected = false;
  642. this._initialize(this._lastServerId!);
  643. } else if (this.destroyed) {
  644. throw new Error(
  645. "This peer cannot reconnect to the server. It has already been destroyed.",
  646. );
  647. } else if (!this.disconnected && !this.open) {
  648. // Do nothing. We're still connecting the first time.
  649. logger.error(
  650. "In a hurry? We're still trying to make the initial connection!",
  651. );
  652. } else {
  653. throw new Error(
  654. `Peer ${this.id} cannot reconnect because it is not disconnected from the server!`,
  655. );
  656. }
  657. }
  658. /**
  659. * Get a list of available peer IDs. If you're running your own server, you'll
  660. * want to set allow_discovery: true in the PeerServer options. If you're using
  661. * the cloud server, email team@peerjs.com to get the functionality enabled for
  662. * your key.
  663. */
  664. listAllPeers(cb = (_: any[]) => {}): void {
  665. this._api
  666. .listAllPeers()
  667. .then((peers) => cb(peers))
  668. .catch((error) => this._abort(PeerErrorType.ServerError, error));
  669. }
  670. }