Skip to content

Commit

Permalink
feat: use native webrtc states
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniow committed Jun 10, 2024
1 parent 29120d5 commit eefe004
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 138 deletions.
11 changes: 6 additions & 5 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"codecov",
"commitlint",
"cpaas",
"createansweronsuccess",
"createofferonsuccess",
"dbaeumer",
"dependabot",
"eamodio",
Expand All @@ -25,22 +27,21 @@
"libauth",
"mkdir",
"negotiatedneeded",
"peerconnectionstatechange",
"preprocessors",
"prettierignore",
"rohit",
"sandboxed",
"saucelabs",
"setlocaldescriptiononsuccess",
"setremotedescriptiononsuccess",
"transpiled",
"typedoc",
"untracked",
"Wcme",
"WCME",
"webex",
"webrtc",
"createofferonsuccess",
"createansweronsuccess",
"setlocaldescriptiononsuccess",
"setremotedescriptiononsuccess"
"webrtc"
],
"flagWords": [],
"ignorePaths": [
Expand Down
69 changes: 18 additions & 51 deletions src/connection-state-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
ConnectionState,
ConnectionStateHandler,
IceConnectionState,
} from './connection-state-handler';
import { ConnectionState, ConnectionStateHandler } from './connection-state-handler';

describe('ConnectionStateHandler', () => {
let fakeIceState: RTCIceConnectionState;
Expand All @@ -21,82 +17,53 @@ describe('ConnectionStateHandler', () => {
fakeConnectionState = 'new';
});

it('reads initial connection state', () => {
it('reads initial peer connection state', () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

expect(connStateHandler.getConnectionState()).toStrictEqual(ConnectionState.New);
expect(connStateHandler.getPeerConnectionState()).toBe('new');
});

it('reads initial ice connection state', () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

expect(connStateHandler.getIceConnectionState()).toStrictEqual(IceConnectionState.New);
expect(connStateHandler.getIceConnectionState()).toBe('new');
});

it('reads initial connection state', () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

expect(connStateHandler.getConnectionState()).toBe('New');
});

it('updates ice connection state on ice connection state change and emits the event', () => {
expect.assertions(2);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

connStateHandler.on(ConnectionStateHandler.Events.IceConnectionStateChanged, (state) => {
expect(state).toStrictEqual(IceConnectionState.Checking);
expect(state).toBe('checking');
});

fakeIceState = 'checking';
connStateHandler.onIceConnectionStateChange();

expect(connStateHandler.getIceConnectionState()).toStrictEqual(IceConnectionState.Checking);
expect(connStateHandler.getIceConnectionState()).toBe('checking');
});

it("updates connection state on RTCPeerConnection's connection state change", () => {
expect.assertions(2);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

connStateHandler.on(ConnectionStateHandler.Events.ConnectionStateChanged, (state) => {
expect(state).toStrictEqual(ConnectionState.Connecting);
connStateHandler.on(ConnectionStateHandler.Events.PeerConnectionStateChanged, (state) => {
expect(state).toBe('connecting');
});

fakeConnectionState = 'connecting';
connStateHandler.onConnectionStateChange();
connStateHandler.onPeerConnectionStateChange();

expect(connStateHandler.getConnectionState()).toStrictEqual(ConnectionState.Connecting);
});

[
{ iceState: 'new', expected: IceConnectionState.New },
{ iceState: 'checking', expected: IceConnectionState.Checking },
{ iceState: 'connected', expected: IceConnectionState.Connected },
{ iceState: 'completed', expected: IceConnectionState.Completed },
{ iceState: 'failed', expected: IceConnectionState.Failed },
{ iceState: 'disconnected', expected: IceConnectionState.Disconnected },
].forEach(({ iceState, expected }) => {
it(`evaluates iceConnectionState to ${expected} when ice state = ${iceState}`, () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

fakeIceState = iceState as RTCIceConnectionState;

expect(connStateHandler.getIceConnectionState()).toStrictEqual(expected);
});
});

[
{ connState: 'new', expected: ConnectionState.New },
{ connState: 'connecting', expected: ConnectionState.Connecting },
{ connState: 'connected', expected: ConnectionState.Connected },
{ connState: 'disconnected', expected: ConnectionState.Disconnected },
{ connState: 'failed', expected: ConnectionState.Failed },
{ connState: 'closed', expected: ConnectionState.Closed },
].forEach(({ connState, expected }) => {
it(`evaluates ConnectionState to ${expected} when connection state = ${connState}`, () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

fakeConnectionState = connState as RTCPeerConnectionState;

expect(connStateHandler.getConnectionState()).toStrictEqual(expected);
});
expect(connStateHandler.getPeerConnectionState()).toBe('connecting');
});

// test matrix for all possible combinations of iceConnectionState and connectionState
Expand Down Expand Up @@ -165,7 +132,7 @@ describe('ConnectionStateHandler', () => {
fakeConnectionState = connState;
fakeIceState = iceState;

expect(connStateHandler.getOverallConnectionState()).toStrictEqual(expected);
expect(connStateHandler.getConnectionState()).toStrictEqual(expected);
})
);
});
63 changes: 20 additions & 43 deletions src/connection-state-handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EventEmitter, EventMap } from './event-emitter';
import { logger } from './util/logger';

export enum OverallConnectionState {
export enum ConnectionState {
New = 'New',
Connecting = 'Connecting',
Connected = 'Connected',
Expand All @@ -10,33 +10,14 @@ export enum OverallConnectionState {
Closed = 'Closed',
}

export enum ConnectionState {
New = 'New', // connection attempt has not been started
Closed = 'Closed', // connection closed, there is no way to move out of this state
Connected = 'Connected', // both ICE and DTLS connections are established, media is flowing
Connecting = 'Connecting', // initial connection attempt in progress
Disconnected = 'Disconnected', // connection lost temporarily, the browser is trying to re-establish it automatically
Failed = 'Failed', // connection failed, an ICE restart is required
}

export enum IceConnectionState {
New = 'New',
Checking = 'Checking',
Connected = 'Connected',
Completed = 'Completed',
Failed = 'Failed',
Disconnected = 'Disconnected',
Closed = 'Closed',
}

enum ConnectionStateEvents {
ConnectionStateChanged = 'ConnectionStateChanged',
PeerConnectionStateChanged = 'PeerConnectionStateChanged',
IceConnectionStateChanged = 'IceConnectionStateChanged',
}

interface ConnectionStateEventHandlers extends EventMap {
[ConnectionStateEvents.ConnectionStateChanged]: (state: ConnectionState) => void;
[ConnectionStateEvents.IceConnectionStateChanged]: (state: IceConnectionState) => void;
[ConnectionStateEvents.PeerConnectionStateChanged]: (state: RTCPeerConnectionState) => void;
[ConnectionStateEvents.IceConnectionStateChanged]: (state: RTCIceConnectionState) => void;
}

type GetCurrentStatesCallback = () => {
Expand Down Expand Up @@ -67,10 +48,10 @@ export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHan
/**
* Handler for connection state change.
*/
public onConnectionStateChange(): void {
const state = this.getConnectionState();
public onPeerConnectionStateChange(): void {
const state = this.getPeerConnectionState();

this.emit(ConnectionStateEvents.ConnectionStateChanged, state);
this.emit(ConnectionStateEvents.PeerConnectionStateChanged, state);
}

/**
Expand All @@ -88,25 +69,25 @@ export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHan
*
* @returns Current overall connection state.
*/
private evaluateMediaConnectionState(): OverallConnectionState {
private evaluateMediaConnectionState(): ConnectionState {
const { connectionState, iceState } = this.getCurrentStatesCallback();

const connectionStates = [connectionState, iceState];

let mediaConnectionState: OverallConnectionState;
let mediaConnectionState: ConnectionState;

if (connectionStates.every((value) => value === 'new')) {
mediaConnectionState = OverallConnectionState.New;
mediaConnectionState = ConnectionState.New;
} else if (connectionStates.some((value) => value === 'closed')) {
mediaConnectionState = OverallConnectionState.Closed;
mediaConnectionState = ConnectionState.Closed;
} else if (connectionStates.some((value) => value === 'failed')) {
mediaConnectionState = OverallConnectionState.Failed;
mediaConnectionState = ConnectionState.Failed;
} else if (connectionStates.some((value) => value === 'disconnected')) {
mediaConnectionState = OverallConnectionState.Disconnected;
mediaConnectionState = ConnectionState.Disconnected;
} else if (connectionStates.every((value) => value === 'connected' || value === 'completed')) {
mediaConnectionState = OverallConnectionState.Connected;
mediaConnectionState = ConnectionState.Connected;
} else {
mediaConnectionState = OverallConnectionState.Connecting;
mediaConnectionState = ConnectionState.Connecting;
}

logger.log(
Expand All @@ -121,33 +102,29 @@ export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHan
*
* @returns Current connection state.
*/
public getConnectionState(): ConnectionState {
public getPeerConnectionState(): RTCPeerConnectionState {
const { connectionState } = this.getCurrentStatesCallback();

const state = connectionState[0].toUpperCase() + connectionState.slice(1);

return state as ConnectionState;
return connectionState;
}

/**
* Gets current ice connection state.
*
* @returns Current ice connection state.
*/
public getIceConnectionState(): IceConnectionState {
public getIceConnectionState(): RTCIceConnectionState {
const { iceState } = this.getCurrentStatesCallback();

const state = iceState[0].toUpperCase() + iceState.slice(1);

return state as IceConnectionState;
return iceState;
}

/**
* Gets current overall connection state.
*
* @returns Current overall connection state.
*/
public getOverallConnectionState(): OverallConnectionState {
public getConnectionState(): ConnectionState {
return this.evaluateMediaConnectionState();
}
}
26 changes: 11 additions & 15 deletions src/peer-connection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { BrowserInfo } from '@webex/web-capabilities';
import { MockedObjectDeep } from 'ts-jest';
import {
ConnectionState,
ConnectionStateHandler,
IceConnectionState,
} from './connection-state-handler';
import { ConnectionStateHandler } from './connection-state-handler';
import { mocked } from './mocks/mock';
import { RTCPeerConnectionStub } from './mocks/rtc-peer-connection-stub';
import { PeerConnection } from './peer-connection';
Expand Down Expand Up @@ -223,39 +219,39 @@ describe('PeerConnection', () => {

mockPc.onconnectionstatechange();

expect(connectionStateHandler.onConnectionStateChange).toHaveBeenCalledTimes(1);
expect(connectionStateHandler.onPeerConnectionStateChange).toHaveBeenCalledTimes(1);
});
it('returns connection state from connection state handler when geConnectionState() is called', () => {
expect.assertions(2);
const connectionStateHandler = getInstantiatedConnectionStateHandler();
connectionStateHandler.getConnectionState.mockReturnValueOnce(ConnectionState.Connected);
connectionStateHandler.getConnectionState.mockReturnValueOnce('connected');

expect(pc.getConnectionState()).toStrictEqual(ConnectionState.Connected);
expect(pc.getConnectionState()).toBe('connected');
expect(connectionStateHandler.getConnectionState).toHaveBeenCalledTimes(1);
});
it("listens on ConnectionStateHandler's ConnectionStateChange event and emits it", () => {
it("listens on ConnectionStateHandler's PeerConnectionStateChange event and emits it", () => {
expect.assertions(2);
const connectionStateHandler = getInstantiatedConnectionStateHandler();

pc.on(PeerConnection.Events.ConnectionStateChange, (state) => {
expect(state).toStrictEqual(ConnectionState.Connecting);
pc.on(PeerConnection.Events.PeerConnectionStateChange, (state) => {
expect(state).toBe('connecting');
});

// verify that PeerConnection listens for the right event
expect(connectionStateHandler.on.mock.calls[0][0]).toStrictEqual(
ConnectionStateHandler.Events.ConnectionStateChanged
ConnectionStateHandler.Events.PeerConnectionStateChanged
);

// trigger the fake event from ConnectionStateHandler
const connectionStateHandlerListener = connectionStateHandler.on.mock.calls[0][1];
connectionStateHandlerListener(ConnectionState.Connecting);
connectionStateHandlerListener('connecting');
});
it("listens on ConnectionStateHandler's IceConnectionStateChange event and emits it", () => {
expect.assertions(2);
const connectionStateHandler = getInstantiatedConnectionStateHandler();

pc.on(PeerConnection.Events.IceConnectionStateChange, (state) => {
expect(state).toStrictEqual(IceConnectionState.Checking);
expect(state).toBe('checking');
});

// verify that PeerConnection listens for the right event
Expand All @@ -265,7 +261,7 @@ describe('PeerConnection', () => {

// trigger the fake event from ConnectionStateHandler
const connectionStateHandlerListener = connectionStateHandler.on.mock.calls[1][1];
connectionStateHandlerListener(IceConnectionState.Checking);
connectionStateHandlerListener('checking');
});
});
describe('createAnswer', () => {
Expand Down
Loading

0 comments on commit eefe004

Please sign in to comment.