Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: set/create events #71

Merged
merged 9 commits into from
Feb 14, 2024
5 changes: 4 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
"Wcme",
"WCME",
"webex",
"webrtc"
"webrtc",
"createofferonsuccess",
"setlocaldescriptiononsuccess",
"setremotedescriptiononsuccess"
],
"flagWords": [],
"ignorePaths": [
Expand Down
10 changes: 9 additions & 1 deletion src/mocks/rtc-peer-connection-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ class RTCPeerConnectionStub {
getStats(): Promise<any> {
return new Promise(() => {});
}
setLocalDescription(): Promise<any> {
setLocalDescription(
description?: RTCSessionDescription | RTCSessionDescriptionInit
): Promise<void> {
return new Promise(() => {});
}

setRemoteDescription(
description?: RTCSessionDescription | RTCSessionDescriptionInit
): Promise<void> {
return new Promise(() => {});
}
onconnectionstatechange: () => void = () => {};
Expand Down
129 changes: 128 additions & 1 deletion src/peer-connection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BrowserInfo } from '@webex/web-capabilities';
import { MockedObjectDeep } from 'ts-jest';
import { ConnectionState, ConnectionStateHandler } from './connection-state-handler';
import { mocked } from './mocks/mock';
import { RTCPeerConnectionStub } from './mocks/rtc-peer-connection-stub';
Expand Down Expand Up @@ -247,17 +248,76 @@ describe('PeerConnection', () => {
});
});

describe('createOffer', () => {
let mockPc: MockedObjectDeep<RTCPeerConnectionStub>;
let createOfferSpy: jest.SpyInstance;
const callback = jest.fn();
let pc: PeerConnection;
const mockedReturnedOffer: RTCSessionDescriptionInit = {
sdp: 'blah',
type: 'offer',
};

beforeEach(() => {
jest.clearAllMocks();
mockPc = mocked(new RTCPeerConnectionStub(), true);
mockPc.createOffer.mockImplementation((description) => {
return new Promise((resolve, reject) => {
if (!description) {
reject(new Error());
} else {
resolve(mockedReturnedOffer);
}
});
});
mockCreateRTCPeerConnection.mockReturnValueOnce(mockPc as unknown as RTCPeerConnection);
pc = new PeerConnection();
createOfferSpy = jest.spyOn(pc, 'createOffer');
pc.on(PeerConnection.Events.CreateOfferOnSuccess, callback);
});

it('should emit event when createOffer called', async () => {
expect.hasAssertions();
const options: RTCOfferOptions = {
iceRestart: true,
};
const offer = await pc.createOffer(options);
expect(offer).toStrictEqual(mockedReturnedOffer);
expect(createOfferSpy).toHaveBeenCalledWith(options);
expect(callback).toHaveBeenCalledWith(mockedReturnedOffer);
});

it('should not emit event when createOffer failed', async () => {
expect.hasAssertions();
const offerPromise = pc.createOffer(null as unknown as RTCOfferOptions);
await expect(offerPromise).rejects.toThrow(Error);
expect(createOfferSpy).toHaveBeenCalledWith(null);
expect(callback).toHaveBeenCalledTimes(0);
});
});

describe('setLocalDescription', () => {
let mockPc: RTCPeerConnectionStub;
let mockPc: MockedObjectDeep<RTCPeerConnectionStub>;
let setLocalDescriptionSpy: jest.SpyInstance;
const callback = jest.fn();
let pc: PeerConnection;

beforeEach(() => {
jest.clearAllMocks();
mockPc = mocked(new RTCPeerConnectionStub(), true);
mockCreateRTCPeerConnection.mockReturnValueOnce(mockPc as unknown as RTCPeerConnection);
mockPc.setLocalDescription.mockImplementation((description) => {
SomeBody16 marked this conversation as resolved.
Show resolved Hide resolved
return new Promise((resolve, reject) => {
if (description?.sdp === 'reject') {
reject(new Error());
} else {
resolve();
}
});
});
setLocalDescriptionSpy = jest.spyOn(mockPc, 'setLocalDescription');
pc = new PeerConnection();
pc.on(PeerConnection.Events.SetLocalDescriptionOnSuccess, callback);
});

it('sets the local description with an SDP offer', async () => {
Expand All @@ -278,5 +338,72 @@ describe('PeerConnection', () => {
pc.setLocalDescription({ type: 'offer', sdp: 'm=video 9 UDP/TLS/RTP' })
).rejects.toThrow(Error);
});

it('should emit event when setLocalDescription called', async () => {
expect.hasAssertions();
const options = {
sdp: 'blah',
};
await pc.setLocalDescription(options as unknown as RTCSessionDescriptionInit);
expect(setLocalDescriptionSpy).toHaveBeenCalledWith(options);
expect(callback).toHaveBeenCalledWith(options);
});

it('should not emit event when setLocalDescription failed', async () => {
expect.hasAssertions();
const options = {
sdp: 'reject',
};
const offerPromise = pc.setLocalDescription(options as unknown as RTCSessionDescriptionInit);
await expect(offerPromise).rejects.toThrow(Error);
expect(setLocalDescriptionSpy).toHaveBeenCalledWith(options);
expect(callback).toHaveBeenCalledTimes(0);
});
});

describe('setRemoteDescription', () => {
let mockPc: MockedObjectDeep<RTCPeerConnectionStub>;
let setRemoteDescriptionSpy: jest.SpyInstance;
const callback = jest.fn();
let pc: PeerConnection;

beforeEach(() => {
jest.clearAllMocks();
mockPc = mocked(new RTCPeerConnectionStub(), true);
mockPc.setRemoteDescription.mockImplementation((description) => {
return new Promise((resolve, reject) => {
if (description?.sdp === 'reject') {
reject(new Error());
} else {
resolve();
}
});
});
mockCreateRTCPeerConnection.mockReturnValueOnce(mockPc as unknown as RTCPeerConnection);
pc = new PeerConnection();
setRemoteDescriptionSpy = jest.spyOn(pc, 'setRemoteDescription');
pc.on(PeerConnection.Events.SetRemoteDescriptionOnSuccess, callback);
});

it('should emit event when setRemoteDescription called', async () => {
expect.hasAssertions();
const options = {
sdp: 'blah',
};
await pc.setRemoteDescription(options as unknown as RTCSessionDescriptionInit);
expect(setRemoteDescriptionSpy).toHaveBeenCalledWith(options);
expect(callback).toHaveBeenCalledWith(options);
});

it('should not emit event when setRemoteDescription failed', async () => {
expect.hasAssertions();
const options = {
sdp: 'reject',
};
const offerPromise = pc.setRemoteDescription(options as unknown as RTCSessionDescriptionInit);
await expect(offerPromise).rejects.toThrow(Error);
expect(setRemoteDescriptionSpy).toHaveBeenCalledWith(options);
expect(callback).toHaveBeenCalledTimes(0);
});
});
});
26 changes: 23 additions & 3 deletions src/peer-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ConnectionState, ConnectionStateHandler } from './connection-state-hand
import { EventEmitter, EventMap } from './event-emitter';
import { createRTCPeerConnection } from './rtc-peer-connection-factory';
import { logger } from './util/logger';

/**
* A type-safe form of the DOMString used in the MediaStreamTrack.kind field.
*/
Expand All @@ -27,11 +28,21 @@ type IceGatheringStateChangeEvent = {
enum PeerConnectionEvents {
IceGatheringStateChange = 'icegatheringstatechange',
ConnectionStateChange = 'connectionstatechange',
CreateOfferOnSuccess = 'createofferonsuccess',
SetLocalDescriptionOnSuccess = 'setlocaldescriptiononsuccess',
SetRemoteDescriptionOnSuccess = 'setremotedescriptiononsuccess',
}

interface PeerConnectionEventHandlers extends EventMap {
[PeerConnectionEvents.IceGatheringStateChange]: (ev: IceGatheringStateChangeEvent) => void;
[PeerConnectionEvents.ConnectionStateChange]: (state: ConnectionState) => void;
[PeerConnectionEvents.CreateOfferOnSuccess]: (offer: RTCSessionDescriptionInit) => void;
[PeerConnectionEvents.SetLocalDescriptionOnSuccess]: (
description: RTCSessionDescription | RTCSessionDescriptionInit
) => void;
[PeerConnectionEvents.SetRemoteDescriptionOnSuccess]: (
description: RTCSessionDescription | RTCSessionDescriptionInit
) => void;
}

type ConnectionType = 'UDP' | 'TCP' | 'TURN-TLS' | 'TURN-TCP' | 'TURN-UDP' | 'unknown';
Expand Down Expand Up @@ -186,7 +197,10 @@ class PeerConnection extends EventEmitter<PeerConnectionEventHandlers> {
* That received offer should be delivered through the signaling server to a remote peer.
*/
async createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit> {
return this.pc.createOffer(options);
return this.pc.createOffer(options).then((offer) => {
this.emit(PeerConnection.Events.CreateOfferOnSuccess, offer);
return offer;
});
}

/**
Expand Down Expand Up @@ -215,7 +229,11 @@ class PeerConnection extends EventEmitter<PeerConnectionEventHandlers> {
});
}

return this.pc.setLocalDescription(description);
return this.pc.setLocalDescription(description).then(() => {
if (description) {
this.emit(PeerConnection.Events.SetLocalDescriptionOnSuccess, description);
}
});
}

/**
Expand All @@ -230,7 +248,9 @@ class PeerConnection extends EventEmitter<PeerConnectionEventHandlers> {
async setRemoteDescription(
description: RTCSessionDescription | RTCSessionDescriptionInit
): Promise<void> {
return this.pc.setRemoteDescription(description);
return this.pc.setRemoteDescription(description).then(() => {
this.emit(PeerConnection.Events.SetRemoteDescriptionOnSuccess, description);
});
}

/**
Expand Down
Loading