Jonas Gloning 1 рік тому
батько
коміт
2cba46de28
3 змінених файлів з 148 додано та 57 видалено
  1. 48 0
      e2e/peer/id-taken.await.html
  2. 7 0
      e2e/peer/peer.spec.ts
  3. 93 57
      lib/peer.ts

+ 48 - 0
e2e/peer/id-taken.await.html

@@ -0,0 +1,48 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title></title>
+		<link rel="stylesheet" href="../style.css" />
+	</head>
+	<body>
+		<h1>ID-TAKEN</h1>
+		<div id="messages"></div>
+		<div id="error-message"></div>
+		<script src="/dist/peerjs.js"></script>
+		<script type="application/javascript">
+			(async () => {
+				/**
+				 * @type {typeof import("../..").Peer}
+				 */
+				const Peer = window.peerjs.Peer;
+
+				const messages = document.getElementById("messages");
+				const errorMessage = document.getElementById("error-message");
+
+				// Peer A should be created without an error
+				try {
+					const peerA = await new Peer();
+					// Create 10 new `Peer`s that will try to steel A's id
+					let peers_try_to_take = Array.from({ length: 10 }, async (_, i) => {
+						try {
+							await new Peer(peerA.id);
+							throw `Peer ${i} failed! Connection got established.`;
+						} catch (error) {
+							if (error.type === "unavailable-id") {
+								return `ID already taken. (${i})`;
+							} else {
+								throw error;
+							}
+						}
+					});
+					await Promise.all(peers_try_to_take);
+					messages.textContent = "No ID takeover";
+				} catch (error) {
+					errorMessage.textContent += JSON.stringify(error);
+				}
+			})();
+		</script>
+	</body>
+</html>

+ 7 - 0
e2e/peer/peer.spec.ts

@@ -23,3 +23,10 @@ describe("Peer", () => {
 		expect(await P.errorMessage.getText()).toBe('{"type":"peer-unavailable"}');
 	});
 });
+describe("Peer:async", () => {
+	it("should emit an error, when the ID is already taken", async () => {
+		await P.open("id-taken.await");
+		await P.waitForMessage("No ID takeover");
+		expect(await P.errorMessage.getText()).toBe("");
+	});
+});

+ 93 - 57
lib/peer.ts

@@ -108,10 +108,83 @@ export interface PeerEvents {
 	 */
 	error: (error: PeerError<`${PeerErrorType}`>) => void;
 }
+
+export interface IPeer {
+	/**
+	 * The brokering ID of this peer
+	 *
+	 * If no ID was specified in {@apilink Peer | the constructor},
+	 * this will be `undefined` until the {@apilink PeerEvents | `open`} event is emitted.
+	 */
+	get id(): string;
+	get open(): boolean;
+	/**
+	 * A hash of all connections associated with this peer, keyed by the remote peer's ID.
+	 * @deprecated
+	 * Return type will change from Object to Map<string,[]>
+	 */
+	get connections(): Object;
+	/**
+	 * true if this peer and all of its connections can no longer be used.
+	 */
+	get destroyed(): boolean;
+	/**
+	 * Connects to the remote peer specified by id and returns a data connection.
+	 *
+	 * Make sure to listen to the `error` event of the resulting {@link DataConnection}
+	 * in case the connection fails.
+	 *
+	 * @param peer The brokering ID of the remote peer (their {@link Peer.id}).
+	 * @param options for specifying details about Peer Connection
+	 */
+	connect(peer: string, options: PeerConnectOption): DataConnection;
+	/**
+	 * Calls the remote peer specified by id and returns a media connection.
+	 * @param peer The brokering ID of the remote peer (their peer.id).
+	 * @param stream The caller's media stream
+	 * @param options Metadata associated with the connection, passed in by whoever initiated the connection.
+	 */
+	call(peer: string, stream: MediaStream, options: CallOption): MediaConnection;
+	/** Retrieve a data/media connection for this peer. */
+	getConnection(
+		peerId: string,
+		connectionId: string,
+	): null | DataConnection | MediaConnection;
+	/**
+	 * Destroys the Peer: closes all active connections as well as the connection
+	 * to the server.
+	 *
+	 * :::caution
+	 * This cannot be undone; the respective peer object will no longer be able
+	 * to create or receive any connections, its ID will be forfeited on the server,
+	 * and all of its data and media connections will be closed.
+	 * :::
+	 */
+	destroy(): void;
+	/**
+	 * Disconnects the Peer's connection to the PeerServer. Does not close any
+	 *  active connections.
+	 * Warning: The peer can no longer create or accept connections after being
+	 *  disconnected. It also cannot reconnect to the server.
+	 */
+	disconnect(): void;
+	/** Attempts to reconnect with the same ID.
+	 *
+	 * Only {@apilink Peer.disconnect | disconnected peers} can be reconnected.
+	 * Destroyed peers cannot be reconnected.
+	 * If the connection fails (as an example, if the peer's old ID is now taken),
+	 * the peer's existing connections will not close, but any associated errors events will fire.
+	 */
+	reconnect(): void;
+}
+
 /**
  * A peer who can initiate connections with other peers.
  */
-export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
+export class Peer
+	extends EventEmitterWithError<PeerErrorType, PeerEvents>
+	implements IPeer
+{
 	private static readonly DEFAULT_KEY = "peerjs";
 
 	protected readonly _serializers: SerializerMapping = {
@@ -138,12 +211,11 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		(DataConnection | MediaConnection)[]
 	> = new Map(); // All connections for this peer.
 	private readonly _lostMessages: Map<string, ServerMessage[]> = new Map(); // src => [list of messages]
-	/**
-	 * The brokering ID of this peer
-	 *
-	 * If no ID was specified in {@apilink Peer | the constructor},
-	 * this will be `undefined` until the {@apilink PeerEvents | `open`} event is emitted.
-	 */
+	private then: (
+		onfulfilled?: (value: IPeer) => any,
+		onrejected?: (reason: PeerError<PeerErrorType>) => any,
+	) => void;
+
 	get id() {
 		return this._id;
 	}
@@ -163,11 +235,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		return this._socket;
 	}
 
-	/**
-	 * A hash of all connections associated with this peer, keyed by the remote peer's ID.
-	 * @deprecated
-	 * Return type will change from Object to Map<string,[]>
-	 */
 	get connections(): Object {
 		const plainConnections = Object.create(null);
 
@@ -178,15 +245,9 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		return plainConnections;
 	}
 
-	/**
-	 * true if this peer and all of its connections can no longer be used.
-	 */
 	get destroyed() {
 		return this._destroyed;
 	}
-	/**
-	 * false if there is an active connection to the PeerServer.
-	 */
 	get disconnected() {
 		return this._disconnected;
 	}
@@ -214,6 +275,20 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 	constructor(id?: string | PeerOptions, options?: PeerOptions) {
 		super();
 
+		this.then = (
+			onfulfilled?: (value: IPeer) => any,
+			onrejected?: (reason: PeerError<PeerErrorType>) => any,
+		) => {
+			// Remove 'then' to prevent potential recursion issues
+			// `await` will wait for a Promise-like to resolve recursively
+			delete this.then;
+
+			// We don’t need to worry about cleaning up listeners here
+			// `await`ing a Promise will make sure only one of the paths executes
+			this.once("open", () => onfulfilled(this));
+			this.once("error", onrejected);
+		};
+
 		let userId: string | undefined;
 
 		// Deal with overloading
@@ -493,15 +568,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		return [];
 	}
 
-	/**
-	 * Connects to the remote peer specified by id and returns a data connection.
-	 *
-	 * Make sure to listen to the `error` event of the resulting {@link DataConnection}
-	 * in case the connection fails.
-	 *
-	 * @param peer The brokering ID of the remote peer (their {@link Peer.id}).
-	 * @param options for specifying details about Peer Connection
-	 */
 	connect(peer: string, options: PeerConnectOption = {}): DataConnection {
 		options = {
 			serialization: "default",
@@ -530,12 +596,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		return dataConnection;
 	}
 
-	/**
-	 * Calls the remote peer specified by id and returns a media connection.
-	 * @param peer The brokering ID of the remote peer (their peer.id).
-	 * @param stream The caller's media stream
-	 * @param options Metadata associated with the connection, passed in by whoever initiated the connection.
-	 */
 	call(
 		peer: string,
 		stream: MediaStream,
@@ -600,7 +660,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		this._lostMessages.delete(connection.connectionId);
 	}
 
-	/** Retrieve a data/media connection for this peer. */
 	getConnection(
 		peerId: string,
 		connectionId: string,
@@ -642,16 +701,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		}
 	}
 
-	/**
-	 * Destroys the Peer: closes all active connections as well as the connection
-	 * to the server.
-	 *
-	 * :::caution
-	 * This cannot be undone; the respective peer object will no longer be able
-	 * to create or receive any connections, its ID will be forfeited on the server,
-	 * and all of its data and media connections will be closed.
-	 * :::
-	 */
 	destroy(): void {
 		if (this.destroyed) {
 			return;
@@ -688,12 +737,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		}
 	}
 
-	/**
-	 * Disconnects the Peer's connection to the PeerServer. Does not close any
-	 *  active connections.
-	 * Warning: The peer can no longer create or accept connections after being
-	 *  disconnected. It also cannot reconnect to the server.
-	 */
 	disconnect(): void {
 		if (this.disconnected) {
 			return;
@@ -714,13 +757,6 @@ export class Peer extends EventEmitterWithError<PeerErrorType, PeerEvents> {
 		this.emit("disconnected", currentId);
 	}
 
-	/** Attempts to reconnect with the same ID.
-	 *
-	 * Only {@apilink Peer.disconnect | disconnected peers} can be reconnected.
-	 * Destroyed peers cannot be reconnected.
-	 * If the connection fails (as an example, if the peer's old ID is now taken),
-	 * the peer's existing connections will not close, but any associated errors events will fire.
-	 */
 	reconnect(): void {
 		if (this.disconnected && !this.destroyed) {
 			logger.log(