mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
479 lines (478 loc) • 18.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const pick_port_1 = require("pick-port");
const mediasoup = require("../");
const enhancedEvents_1 = require("../enhancedEvents");
const errors_1 = require("../errors");
const ctx = {};
beforeEach(async () => {
ctx.worker = await mediasoup.createWorker();
});
afterEach(async () => {
ctx.worker?.close();
if (ctx.worker?.subprocessClosed === false) {
await (0, enhancedEvents_1.enhancedOnce)(ctx.worker, 'subprocessclose');
}
});
test('worker.createWebRtcServer() succeeds', async () => {
const onObserverNewWebRtcServer = jest.fn();
ctx.worker.observer.once('newwebrtcserver', onObserverNewWebRtcServer);
const port1 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const port2 = await (0, pick_port_1.pickPort)({
type: 'tcp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
port: port1,
},
{
protocol: 'tcp',
ip: '127.0.0.1',
announcedAddress: 'foo.bar.org',
port: port2,
},
],
appData: { foo: 123 },
});
expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1);
expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer);
expect(typeof webRtcServer.id).toBe('string');
expect(webRtcServer.closed).toBe(false);
expect(webRtcServer.appData).toEqual({ foo: 123 });
await expect(ctx.worker.dump()).resolves.toMatchObject({
pid: ctx.worker.pid,
webRtcServerIds: [webRtcServer.id],
routerIds: [],
channelMessageHandlers: {
channelRequestHandlers: [webRtcServer.id],
channelNotificationHandlers: [],
},
});
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: port1 }],
tcpServers: [{ ip: '127.0.0.1', port: port2 }],
webRtcTransportIds: [],
localIceUsernameFragments: [],
tupleHashes: [],
});
// Private API.
expect(ctx.worker.webRtcServersForTesting.size).toBe(1);
ctx.worker.close();
expect(webRtcServer.closed).toBe(true);
// Private API.
expect(ctx.worker.webRtcServersForTesting.size).toBe(0);
}, 2000);
test('worker.createWebRtcServer() with portRange succeeds', async () => {
const onObserverNewWebRtcServer = jest.fn();
ctx.worker.observer.once('newwebrtcserver', onObserverNewWebRtcServer);
const port1 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const port2 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
portRange: { min: port1, max: port1 },
},
{
protocol: 'tcp',
ip: '127.0.0.1',
announcedAddress: '1.2.3.4',
portRange: { min: port2, max: port2 },
},
],
appData: { foo: 123 },
});
expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1);
expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer);
expect(typeof webRtcServer.id).toBe('string');
expect(webRtcServer.closed).toBe(false);
expect(webRtcServer.appData).toEqual({ foo: 123 });
await expect(ctx.worker.dump()).resolves.toMatchObject({
pid: ctx.worker.pid,
webRtcServerIds: [webRtcServer.id],
routerIds: [],
channelMessageHandlers: {
channelRequestHandlers: [webRtcServer.id],
channelNotificationHandlers: [],
},
});
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: port1 }],
tcpServers: [{ ip: '127.0.0.1', port: port2 }],
webRtcTransportIds: [],
localIceUsernameFragments: [],
tupleHashes: [],
});
// Private API.
expect(ctx.worker.webRtcServersForTesting.size).toBe(1);
ctx.worker.close();
expect(webRtcServer.closed).toBe(true);
// Private API.
expect(ctx.worker.webRtcServersForTesting.size).toBe(0);
}, 2000);
test('worker.createWebRtcServer() without specifying port/portRange succeeds', async () => {
const onObserverNewWebRtcServer = jest.fn();
ctx.worker.observer.once('newwebrtcserver', onObserverNewWebRtcServer);
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
},
{
protocol: 'tcp',
ip: '127.0.0.1',
announcedAddress: '1.2.3.4',
},
],
appData: { foo: 123 },
});
expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1);
expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer);
expect(typeof webRtcServer.id).toBe('string');
expect(webRtcServer.closed).toBe(false);
expect(webRtcServer.appData).toEqual({ foo: 123 });
await expect(ctx.worker.dump()).resolves.toMatchObject({
pid: ctx.worker.pid,
webRtcServerIds: [webRtcServer.id],
routerIds: [],
channelMessageHandlers: {
channelRequestHandlers: [webRtcServer.id],
channelNotificationHandlers: [],
},
});
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: expect.any(Number) }],
tcpServers: [{ ip: '127.0.0.1', port: expect.any(Number) }],
webRtcTransportIds: [],
localIceUsernameFragments: [],
tupleHashes: [],
});
// Private API.
expect(ctx.worker.webRtcServersForTesting.size).toBe(1);
ctx.worker.close();
expect(webRtcServer.closed).toBe(true);
// Private API.
expect(ctx.worker.webRtcServersForTesting.size).toBe(0);
}, 2000);
test('worker.createWebRtcServer() with wrong arguments rejects with TypeError', async () => {
// @ts-ignore
await expect(ctx.worker.createWebRtcServer({})).rejects.toThrow(TypeError);
await expect(
// @ts-ignore
ctx.worker.createWebRtcServer({ listenInfos: 'NOT-AN-ARRAY' })).rejects.toThrow(TypeError);
await expect(
// @ts-ignore
ctx.worker.createWebRtcServer({ listenInfos: ['NOT-AN-OBJECT'] })).rejects.toThrow(Error);
// Empty listenInfos so should fail.
await expect(ctx.worker.createWebRtcServer({ listenInfos: [] })).rejects.toThrow(TypeError);
}, 2000);
test('worker.createWebRtcServer() with unavailable listenInfos rejects with Error', async () => {
const worker2 = await mediasoup.createWorker();
const port1 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const port2 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
// Using an unavailable listen IP.
await expect(ctx.worker.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
port: port1,
},
{
protocol: 'udp',
ip: '1.2.3.4',
port: port2,
},
],
})).rejects.toThrow(Error);
// Using the same UDP port in two listenInfos.
await expect(ctx.worker.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
port: port1,
},
{
protocol: 'udp',
ip: '127.0.0.1',
announcedAddress: '1.2.3.4',
port: port1,
},
],
})).rejects.toThrow(Error);
await ctx.worker.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
port: port1,
},
],
});
// Using the same UDP port in a second Worker.
await expect(worker2.createWebRtcServer({
listenInfos: [
{
protocol: 'udp',
ip: '127.0.0.1',
port: port1,
},
],
})).rejects.toThrow(Error);
worker2.close();
}, 2000);
test('worker.createWebRtcServer() rejects with InvalidStateError if Worker is closed', async () => {
ctx.worker.close();
const port = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
await expect(ctx.worker.createWebRtcServer({
listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', port }],
})).rejects.toThrow(errors_1.InvalidStateError);
}, 2000);
test('webRtcServer.close() succeeds', async () => {
const port = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', port }],
});
const onObserverClose = jest.fn();
webRtcServer.observer.once('close', onObserverClose);
webRtcServer.close();
expect(onObserverClose).toHaveBeenCalledTimes(1);
expect(webRtcServer.closed).toBe(true);
}, 2000);
test('WebRtcServer emits "workerclose" if Worker is closed', async () => {
const port = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [{ protocol: 'tcp', ip: '127.0.0.1', port }],
});
const onObserverClose = jest.fn();
webRtcServer.observer.once('close', onObserverClose);
const promise = (0, enhancedEvents_1.enhancedOnce)(webRtcServer, 'workerclose');
ctx.worker.close();
await promise;
expect(onObserverClose).toHaveBeenCalledTimes(1);
expect(webRtcServer.closed).toBe(true);
}, 2000);
test('router.createWebRtcTransport() with webRtcServer succeeds and transport is closed', async () => {
const port1 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const port2 = await (0, pick_port_1.pickPort)({
type: 'tcp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [
{ protocol: 'udp', ip: '127.0.0.1', port: port1 },
{ protocol: 'tcp', ip: '127.0.0.1', port: port2 },
],
});
const onObserverWebRtcTransportHandled = jest.fn();
const onObserverWebRtcTransportUnhandled = jest.fn();
webRtcServer.observer.once('webrtctransporthandled', onObserverWebRtcTransportHandled);
webRtcServer.observer.once('webrtctransportunhandled', onObserverWebRtcTransportUnhandled);
const router = await ctx.worker.createRouter();
const onObserverNewTransport = jest.fn();
router.observer.once('newtransport', onObserverNewTransport);
const transport = await router.createWebRtcTransport({
webRtcServer,
// Let's disable UDP so resulting ICE candidates should only contain TCP.
enableUdp: false,
appData: { foo: 'bar' },
});
await expect(router.dump()).resolves.toMatchObject({
transportIds: [transport.id],
});
expect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1);
expect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport);
expect(onObserverNewTransport).toHaveBeenCalledTimes(1);
expect(onObserverNewTransport).toHaveBeenCalledWith(transport);
expect(typeof transport.id).toBe('string');
expect(transport.closed).toBe(false);
expect(transport.appData).toEqual({ foo: 'bar' });
const iceCandidates = transport.iceCandidates;
expect(iceCandidates.length).toBe(1);
expect(iceCandidates[0].ip).toBe('127.0.0.1');
expect(iceCandidates[0].port).toBe(port2);
expect(iceCandidates[0].protocol).toBe('tcp');
expect(iceCandidates[0].type).toBe('host');
expect(iceCandidates[0].tcpType).toBe('passive');
expect(transport.iceState).toBe('new');
expect(transport.iceSelectedTuple).toBeUndefined();
expect(webRtcServer.webRtcTransportsForTesting.size).toBe(1);
expect(router.transportsForTesting.size).toBe(1);
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: port1 }],
tcpServers: [{ ip: '127.0.0.1', port: port2 }],
webRtcTransportIds: [transport.id],
localIceUsernameFragments: [
{ /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id },
],
tupleHashes: [],
});
transport.close();
expect(transport.closed).toBe(true);
expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1);
expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport);
expect(webRtcServer.webRtcTransportsForTesting.size).toBe(0);
expect(router.transportsForTesting.size).toBe(0);
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: port1 }],
tcpServers: [{ ip: '127.0.0.1', port: port2 }],
webRtcTransportIds: [],
localIceUsernameFragments: [],
tupleHashes: [],
});
}, 2000);
test('router.createWebRtcTransport() with webRtcServer succeeds and webRtcServer is closed', async () => {
const port1 = await (0, pick_port_1.pickPort)({
type: 'udp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const port2 = await (0, pick_port_1.pickPort)({
type: 'tcp',
ip: '127.0.0.1',
reserveTimeout: 0,
});
const webRtcServer = await ctx.worker.createWebRtcServer({
listenInfos: [
{ protocol: 'udp', ip: '127.0.0.1', port: port1 },
{ protocol: 'tcp', ip: '127.0.0.1', port: port2 },
],
});
const onObserverWebRtcTransportHandled = jest.fn();
const onObserverWebRtcTransportUnhandled = jest.fn();
webRtcServer.observer.once('webrtctransporthandled', onObserverWebRtcTransportHandled);
webRtcServer.observer.once('webrtctransportunhandled', onObserverWebRtcTransportUnhandled);
const router = await ctx.worker.createRouter();
const transport = await router.createWebRtcTransport({
webRtcServer,
appData: { foo: 'bar' },
});
expect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1);
expect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport);
await expect(router.dump()).resolves.toMatchObject({
transportIds: [transport.id],
});
expect(typeof transport.id).toBe('string');
expect(transport.closed).toBe(false);
expect(transport.appData).toEqual({ foo: 'bar' });
const iceCandidates = transport.iceCandidates;
expect(iceCandidates.length).toBe(2);
expect(iceCandidates[0].ip).toBe('127.0.0.1');
expect(iceCandidates[0].port).toBe(port1);
expect(iceCandidates[0].protocol).toBe('udp');
expect(iceCandidates[0].type).toBe('host');
expect(iceCandidates[0].tcpType).toBeUndefined();
expect(iceCandidates[1].ip).toBe('127.0.0.1');
expect(iceCandidates[1].port).toBe(port2);
expect(iceCandidates[1].protocol).toBe('tcp');
expect(iceCandidates[1].type).toBe('host');
expect(iceCandidates[1].tcpType).toBe('passive');
expect(transport.iceState).toBe('new');
expect(transport.iceSelectedTuple).toBeUndefined();
expect(webRtcServer.webRtcTransportsForTesting.size).toBe(1);
expect(router.transportsForTesting.size).toBe(1);
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: port1 }],
tcpServers: [{ ip: '127.0.0.1', port: port2 }],
webRtcTransportIds: [transport.id],
localIceUsernameFragments: [
{ /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id },
],
tupleHashes: [],
});
// Let's restart ICE in the transport so it should add a new entry in
// localIceUsernameFragments in the WebRtcServer.
await transport.restartIce();
await expect(webRtcServer.dump()).resolves.toMatchObject({
id: webRtcServer.id,
udpSockets: [{ ip: '127.0.0.1', port: port1 }],
tcpServers: [{ ip: '127.0.0.1', port: port2 }],
webRtcTransportIds: [transport.id],
localIceUsernameFragments: [
{ /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id },
{ /* localIceUsernameFragment: yyy, */ webRtcTransportId: transport.id },
],
tupleHashes: [],
});
const onObserverClose = jest.fn();
webRtcServer.observer.once('close', onObserverClose);
const onListenServerClose = jest.fn();
transport.once('listenserverclose', onListenServerClose);
webRtcServer.close();
expect(webRtcServer.closed).toBe(true);
expect(onObserverClose).toHaveBeenCalledTimes(1);
expect(onListenServerClose).toHaveBeenCalledTimes(1);
expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1);
expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport);
expect(transport.closed).toBe(true);
expect(transport.iceState).toBe('closed');
expect(transport.iceSelectedTuple).toBe(undefined);
expect(transport.dtlsState).toBe('closed');
expect(transport.sctpState).toBe(undefined);
expect(webRtcServer.webRtcTransportsForTesting.size).toBe(0);
expect(router.transportsForTesting.size).toBe(0);
await expect(ctx.worker.dump()).resolves.toMatchObject({
pid: ctx.worker.pid,
webRtcServerIds: [],
routerIds: [router.id],
channelMessageHandlers: {
channelRequestHandlers: [router.id],
channelNotificationHandlers: [],
},
});
await expect(router.dump()).resolves.toMatchObject({
id: router.id,
transportIds: [],
});
}, 2000);