From ad3a0cbe8c5346509099116441e6c3ff0b6ca6c4 Mon Sep 17 00:00:00 2001 From: Jonas Gloning <34194370+jonasgloning@users.noreply.github.com> Date: Sat, 2 Sep 2023 12:57:21 +0200 Subject: [PATCH] feat: `PeerError` from connections --- lib/baseconnection.ts | 24 +++++++--- lib/dataconnection/BufferedConnection/Json.ts | 7 ++- lib/dataconnection/DataConnection.ts | 34 +++++++------- lib/enums.ts | 10 +++++ lib/exports.ts | 11 ++--- lib/mediaconnection.ts | 8 ++-- lib/negotiator.ts | 25 ++++++----- lib/peer.ts | 36 ++++----------- lib/peerError.ts | 44 +++++++++++++++++++ 9 files changed, 126 insertions(+), 73 deletions(-) create mode 100644 lib/peerError.ts diff --git a/lib/baseconnection.ts b/lib/baseconnection.ts index ec39d92ce..8c0c18402 100644 --- a/lib/baseconnection.ts +++ b/lib/baseconnection.ts @@ -1,9 +1,17 @@ -import { EventEmitter, type ValidEventTypes } from "eventemitter3"; import type { Peer } from "./peer"; import type { ServerMessage } from "./servermessage"; import type { ConnectionType } from "./enums"; +import { BaseConnectionErrorType } from "./enums"; +import { + EventEmitterWithError, + type EventsWithError, + PeerError, +} from "./peerError"; +import type { ValidEventTypes } from "eventemitter3"; -export type BaseConnectionEvents = { +export interface BaseConnectionEvents< + ErrorType extends string = BaseConnectionErrorType, +> extends EventsWithError { /** * Emitted when either you or the remote peer closes the connection. * @@ -17,13 +25,17 @@ export type BaseConnectionEvents = { * connection.on('error', (error) => { ... }); * ``` */ - error: (error: Error) => void; + error: (error: PeerError<`${ErrorType}`>) => void; iceStateChanged: (state: RTCIceConnectionState) => void; -}; +} export abstract class BaseConnection< - T extends ValidEventTypes, -> extends EventEmitter { + SubClassEvents extends ValidEventTypes, + ErrorType extends string = never, +> extends EventEmitterWithError< + ErrorType | BaseConnectionErrorType, + SubClassEvents & BaseConnectionEvents +> { protected _open = false; /** diff --git a/lib/dataconnection/BufferedConnection/Json.ts b/lib/dataconnection/BufferedConnection/Json.ts index 307787bb8..aaef1bca0 100644 --- a/lib/dataconnection/BufferedConnection/Json.ts +++ b/lib/dataconnection/BufferedConnection/Json.ts @@ -1,5 +1,5 @@ import { BufferedConnection } from "./BufferedConnection"; -import { SerializationType } from "../../enums"; +import { DataConnectionErrorType, SerializationType } from "../../enums"; import { util } from "../../util"; export class Json extends BufferedConnection { @@ -27,7 +27,10 @@ export class Json extends BufferedConnection { override _send(data, _chunked) { const encodedData = this.encoder.encode(this.stringify(data)); if (encodedData.byteLength >= util.chunkedMTU) { - this.emit("error", new Error("Message too big for JSON channel")); + this.emitError( + DataConnectionErrorType.MessageToBig, + "Message too big for JSON channel", + ); return; } this._bufferedSend(encodedData); diff --git a/lib/dataconnection/DataConnection.ts b/lib/dataconnection/DataConnection.ts index 621139e07..863a37598 100644 --- a/lib/dataconnection/DataConnection.ts +++ b/lib/dataconnection/DataConnection.ts @@ -1,13 +1,20 @@ import logger from "../logger"; import { Negotiator } from "../negotiator"; -import { ConnectionType, ServerMessageType } from "../enums"; +import { + BaseConnectionErrorType, + ConnectionType, + DataConnectionErrorType, + ServerMessageType, +} from "../enums"; import type { Peer } from "../peer"; -import { BaseConnection } from "../baseconnection"; +import { BaseConnection, type BaseConnectionEvents } from "../baseconnection"; import type { ServerMessage } from "../servermessage"; -import type { DataConnection as IDataConnection } from "./DataConnection"; +import type { EventsWithError } from "../peerError"; import { randomToken } from "../utils/randomToken"; -type DataConnectionEvents = { +export interface DataConnectionEvents + extends EventsWithError, + BaseConnectionEvents { /** * Emitted when data is received from the remote peer. */ @@ -16,15 +23,15 @@ type DataConnectionEvents = { * Emitted when the connection is established and ready-to-use. */ open: () => void; -}; +} /** * Wraps a DataChannel between two Peers. */ -export abstract class DataConnection - extends BaseConnection - implements IDataConnection -{ +export abstract class DataConnection extends BaseConnection< + DataConnectionEvents, + DataConnectionErrorType +> { protected static readonly ID_PREFIX = "dc_"; protected static readonly MAX_BUFFERED_AMOUNT = 8 * 1024 * 1024; @@ -32,7 +39,6 @@ export abstract class DataConnection abstract readonly serialization: string; readonly reliable: boolean; - // public type: ConnectionType.Data; public get type() { return ConnectionType.Data; } @@ -123,11 +129,9 @@ export abstract class DataConnection /** Allows user to send data. */ public send(data: any, chunked = false) { if (!this.open) { - super.emit( - "error", - new Error( - "Connection is not open. You should listen for the `open` event before sending messages.", - ), + this.emitError( + DataConnectionErrorType.NotOpenYet, + "Connection is not open. You should listen for the `open` event before sending messages.", ); return; } diff --git a/lib/enums.ts b/lib/enums.ts index a19048d17..0394f90aa 100644 --- a/lib/enums.ts +++ b/lib/enums.ts @@ -60,6 +60,16 @@ export enum PeerErrorType { WebRTC = "webrtc", } +export enum BaseConnectionErrorType { + NegotiationFailed = "negotiation-failed", + ConnectionClosed = "connection-closed", +} + +export enum DataConnectionErrorType { + NotOpenYet = "not-open-yet", + MessageToBig = "message-too-big", +} + export enum SerializationType { Binary = "binary", BinaryUTF8 = "binary-utf8", diff --git a/lib/exports.ts b/lib/exports.ts index 8a23c581a..3dd8d5175 100644 --- a/lib/exports.ts +++ b/lib/exports.ts @@ -3,7 +3,7 @@ import { Peer } from "./peer"; import { CborPeer } from "./cborPeer"; import { MsgPackPeer } from "./msgPackPeer"; -export type { PeerEvents, PeerError, PeerOptions } from "./peer"; +export type { PeerEvents, PeerOptions } from "./peer"; export type { PeerJSOption, @@ -15,13 +15,7 @@ export type { UtilSupportsObj } from "./util"; export type { DataConnection } from "./dataconnection/DataConnection"; export type { MediaConnection } from "./mediaconnection"; export type { LogLevel } from "./logger"; -export type { - ConnectionType, - PeerErrorType, - SerializationType, - SocketEventType, - ServerMessageType, -} from "./enums"; +export * from "./enums"; export { BufferedConnection } from "./dataconnection/BufferedConnection/BufferedConnection"; export { StreamConnection } from "./dataconnection/StreamConnection/StreamConnection"; @@ -31,4 +25,5 @@ export type { SerializerMapping } from "./peer"; export { Peer, MsgPackPeer, CborPeer }; +export { PeerError } from "./peerError"; export default Peer; diff --git a/lib/mediaconnection.ts b/lib/mediaconnection.ts index a2dd10574..a138a464c 100644 --- a/lib/mediaconnection.ts +++ b/lib/mediaconnection.ts @@ -3,11 +3,11 @@ import logger from "./logger"; import { Negotiator } from "./negotiator"; import { ConnectionType, ServerMessageType } from "./enums"; import type { Peer } from "./peer"; -import { BaseConnection } from "./baseconnection"; +import { BaseConnection, type BaseConnectionEvents } from "./baseconnection"; import type { ServerMessage } from "./servermessage"; import type { AnswerOption } from "./optionInterfaces"; -export type MediaConnectionEvents = { +export interface MediaConnectionEvents extends BaseConnectionEvents { /** * Emitted when a connection to the PeerServer is established. * @@ -22,7 +22,7 @@ export type MediaConnectionEvents = { * @beta */ willCloseOnRemote: () => void; -}; +} /** * Wraps WebRTC's media streams. @@ -32,7 +32,7 @@ export class MediaConnection extends BaseConnection { private static readonly ID_PREFIX = "mc_"; readonly label: string; - private _negotiator: Negotiator; + private _negotiator: Negotiator; private _localStream: MediaStream; private _remoteStream: MediaStream; diff --git a/lib/negotiator.ts b/lib/negotiator.ts index ab630b14c..6f5f46215 100644 --- a/lib/negotiator.ts +++ b/lib/negotiator.ts @@ -1,7 +1,12 @@ import logger from "./logger"; import type { MediaConnection } from "./mediaconnection"; import type { DataConnection } from "./dataconnection/DataConnection"; -import { ConnectionType, PeerErrorType, ServerMessageType } from "./enums"; +import { + BaseConnectionErrorType, + ConnectionType, + PeerErrorType, + ServerMessageType, +} from "./enums"; import type { BaseConnection, BaseConnectionEvents } from "./baseconnection"; import type { ValidEventTypes } from "eventemitter3"; @@ -9,10 +14,10 @@ import type { ValidEventTypes } from "eventemitter3"; * Manages all negotiations between Peers. */ export class Negotiator< - A extends ValidEventTypes, - T extends BaseConnection, + Events extends ValidEventTypes, + ConnectionType extends BaseConnection, > { - constructor(readonly connection: T) {} + constructor(readonly connection: ConnectionType) {} /** Returns a PeerConnection object set up correctly (for data, media). */ startConnection(options: any) { @@ -88,9 +93,9 @@ export class Negotiator< logger.log( "iceConnectionState is failed, closing connections to " + peerId, ); - this.connection.emit( - "error", - new Error("Negotiation of connection to " + peerId + " failed."), + this.connection.emitError( + BaseConnectionErrorType.NegotiationFailed, + "Negotiation of connection to " + peerId + " failed.", ); this.connection.close(); break; @@ -98,9 +103,9 @@ export class Negotiator< logger.log( "iceConnectionState is closed, closing connections to " + peerId, ); - this.connection.emit( - "error", - new Error("Connection to " + peerId + " closed."), + this.connection.emitError( + BaseConnectionErrorType.ConnectionClosed, + "Connection to " + peerId + " closed.", ); this.connection.close(); break; diff --git a/lib/peer.ts b/lib/peer.ts index 11962b1c0..534147456 100644 --- a/lib/peer.ts +++ b/lib/peer.ts @@ -1,4 +1,3 @@ -import { EventEmitter } from "eventemitter3"; import { util } from "./util"; import logger, { LogLevel } from "./logger"; import { Socket } from "./socket"; @@ -21,6 +20,8 @@ import { BinaryPack } from "./dataconnection/BufferedConnection/BinaryPack"; import { Raw } from "./dataconnection/BufferedConnection/Raw"; import { Json } from "./dataconnection/BufferedConnection/Json"; +import { EventEmitterWithError, PeerError } from "./peerError"; + class PeerOptions implements PeerJSOption { /** * Prints log messages depending on the debug level passed in. @@ -66,21 +67,7 @@ class PeerOptions implements PeerJSOption { serializers?: SerializerMapping; } -class PeerError extends Error { - constructor(type: PeerErrorType, err: Error | string) { - if (typeof err === "string") { - super(err); - } else { - super(); - Object.assign(this, err); - } - - this.type = type; - } - - type: PeerErrorType; -} -export type { PeerError, PeerOptions }; +export { type PeerOptions }; export type SerializerMapping = { [key: string]: new ( @@ -90,7 +77,7 @@ export type SerializerMapping = { ) => DataConnection; }; -export type PeerEvents = { +export interface PeerEvents { /** * Emitted when a connection to the PeerServer is established. * @@ -118,12 +105,12 @@ export type PeerEvents = { * * Errors from the underlying socket and PeerConnections are forwarded here. */ - error: (error: PeerError) => void; -}; + error: (error: PeerError<`${PeerErrorType}`>) => void; +} /** * A peer who can initiate connections with other peers. */ -export class Peer extends EventEmitter { +export class Peer extends EventEmitterWithError { private static readonly DEFAULT_KEY = "peerjs"; protected readonly _serializers: SerializerMapping = { @@ -500,7 +487,7 @@ export class Peer extends EventEmitter { * @param peer The brokering ID of the remote peer (their {@apilink Peer.id}). * @param options for specifying details about Peer Connection */ - connect(peer: string, options: PeerConnectOption): DataConnection { + connect(peer: string, options: PeerConnectOption = {}): DataConnection { options = { serialization: "default", ...options, @@ -640,13 +627,6 @@ export class Peer extends EventEmitter { } } - /** Emits a typed error message. */ - emitError(type: PeerErrorType, err: string | Error): void { - logger.error("Error:", err); - - this.emit("error", new PeerError(type, err)); - } - /** * Destroys the Peer: closes all active connections as well as the connection * to the server. diff --git a/lib/peerError.ts b/lib/peerError.ts new file mode 100644 index 000000000..c174d0c1f --- /dev/null +++ b/lib/peerError.ts @@ -0,0 +1,44 @@ +import { EventEmitter } from "eventemitter3"; +import logger from "./logger"; + +export interface EventsWithError { + error: (error: PeerError<`${ErrorType}`>) => void; +} + +export class EventEmitterWithError< + ErrorType extends string, + Events extends EventsWithError, +> extends EventEmitter { + /** + * Emits a typed error message. + * + * @internal + */ + emitError(type: ErrorType, err: string | Error): void { + logger.error("Error:", err); + + // @ts-ignore + this.emit("error", new PeerError<`${ErrorType}`>(`${type}`, err)); + } +} +/** + * A PeerError is emitted whenever an error occurs. + * It always has a `.type`, which can be used to identify the error. + */ +export class PeerError extends Error { + /** + * @internal + */ + constructor(type: T, err: Error | string) { + if (typeof err === "string") { + super(err); + } else { + super(); + Object.assign(this, err); + } + + this.type = type; + } + + public type: T; +}