@twilio/voice-react-native-sdk
Version:
Twilio Voice React Native SDK
1,495 lines (1,313 loc) • 42 kB
text/typescript
/**
* Copyright © 2025 Twilio, Inc. All rights reserved. Licensed under the Twilio
* license.
*
* See LICENSE in the project root for license information.
*/
import { EventEmitter } from 'eventemitter3';
import type { Call } from './Call';
import * as common from './common';
import { Constants } from './constants';
import { InvalidStateError, TwilioError } from './error';
import { constructTwilioError } from './error/utility';
import type { AudioCodec } from './type/AudioCodec';
import type { IceServer, IceTransportPolicy } from './type/Ice';
import type * as PreflightTestType from './type/PreflightTest';
export interface PreflightTest {
/**
* ------------
* Emit Typings
* ------------
*/
/** @internal */
emit(connectedEvent: PreflightTest.Event.Connected): boolean;
/** @internal */
emit(
completedEvent: PreflightTest.Event.Completed,
report: PreflightTest.Report
): boolean;
/** @internal */
emit(failedEvent: PreflightTest.Event.Failed, error: TwilioError): boolean;
/** @internal */
emit(
sampleEvent: PreflightTest.Event.Sample,
sample: PreflightTest.RTCSample
): boolean;
/** @internal */
emit(
qualityWarningEvent: PreflightTest.Event.QualityWarning,
currentWarnings: Call.QualityWarning[],
previousWarnings: Call.QualityWarning[]
): boolean;
/**
* ----------------
* Listener Typings
* ----------------
*/
/**
* Connected event. Raised when the PreflightTest has successfully connected.
*
* @example
* ```typescript
* preflightTest.addListener(PreflightTest.Event.Connected, () => {
* // preflightTest has been connected
* });
* ```
*
* @param connectedEvent - The raised event string.
* @param listener - A listener function that will be invoked when the event
* is raised.
* @returns - The PreflightTest object.
*/
addListener(
connectedEvent: PreflightTest.Event.Connected,
listener: PreflightTest.Listener.Connected
): this;
/** {@inheritDoc (PreflightTest:interface).(addListener:1)} */
on(
connectedEvent: PreflightTest.Event.Connected,
listener: PreflightTest.Listener.Connected
): this;
/**
* Completed event. Raised when the PreflightTest has successfully completed.
*
* @example
* ```typescript
* preflightTest.addListener(PreflightTest.Event.Completed, (report: PreflightTest.Report) => {
* // preflightTest has been completed
* // consider using the report and adjusting your UI to show any potential issues
* });
* ```
*
* @param completedEvent - The raised event string.
* @param listener - A listener function that will be invoked when the event
* is raised.
* @returns - The PreflightTest object.
*/
addListener(
completedEvent: PreflightTest.Event.Completed,
listener: PreflightTest.Listener.Completed
): this;
/** {@inheritDoc (PreflightTest:interface).(addListener:2)} */
on(
completedEvent: PreflightTest.Event.Completed,
listener: PreflightTest.Listener.Completed
): this;
/**
* Failed event. Raised when the PreflightTest was unable to be performed.
*
* @example
* ```typescript
* preflightTest.addListener(PreflightTest.Event.Failed, (error: TwilioError) => {
* // preflightTest has failed
* // consider adjusting your UI to show the error
* });
* ```
*
* @param failedEvent - The raised event string.
* @param listener - A listener function that will be invoked when the event
* is raised.
* @returns - The PreflightTest object.
*/
addListener(
failedEvent: PreflightTest.Event.Failed,
listener: PreflightTest.Listener.Failed
): this;
/** {@inheritDoc (PreflightTest:interface).(addListener:3)} */
on(
failedEvent: PreflightTest.Event.Failed,
listener: PreflightTest.Listener.Failed
): this;
/**
* Sample event. Raised when the PreflightTest has generated a stats sample
* during the test.
*
* @example
* ```typescript
* preflightTest.addListener(PreflightTest.Event.Sample, (sample: PreflightTest.Sample) => {
* // preflightTest has generated a sample
* // consider updating your UI with information from the sample
* });
* ```
*
* @param sampleEvent - The raised event string.
* @param listener - A listener function that will be invoked when the event
* is raised.
* @returns - The PreflightTest object.
*/
addListener(
sampleEvent: PreflightTest.Event.Sample,
listener: PreflightTest.Listener.Sample
): this;
/** {@inheritDoc (PreflightTest:interface).(addListener:4)} */
on(
sampleEvent: PreflightTest.Event.Sample,
listener: PreflightTest.Listener.Sample
): this;
/**
* QualityWarning event. Raised when the PreflightTest has encountered a
* QualityWarning during a test.
*
* @example
* ```typescript
* preflightTest.addListener(
* PreflightTest.Event.QualityWarning,
* (currentWarnings: Call.QualityWarning[], previousWarnings: Call.QualityWarning[]) => {
* // preflightTest has generated or cleared a quality warning
* // consider updating your UI with information about the warning
* },
* );
* ```
*
* @param qualityWarningEvent - The raised event string.
* @param listener - A listener function that will be invoked when the event
* is raised.
* @returns - The PreflightTest object.
*/
addListener(
warningEvent: PreflightTest.Event.QualityWarning,
listener: PreflightTest.Listener.QualityWarning
): this;
/** {@inheritDoc (PreflightTest:interface).(addListener:5)} */
on(
warningEvent: PreflightTest.Event.QualityWarning,
listener: PreflightTest.Listener.QualityWarning
): this;
/**
* Generic event listener typings.
* @param preflightTestEvent - The raised event string.
* @param listener - A listener function that will be invoked when the event
* is raised.
* @returns - The PreflightTest object.
*/
addListener(
event: PreflightTest.Event,
listener: PreflightTest.Listener.Generic
): this;
/** {@inheritDoc (PreflightTest:interface).(addListener:6)} */
on(
event: PreflightTest.Event,
listener: PreflightTest.Listener.Generic
): this;
}
/**
* The PreflightTest for Voice React Native SDK allows you to anticipate and
* troubleshoot end users' connectivity and bandwidth issues before or during
* Twilio Voice calls.
*
* You can run a PreflightTest before a Twilio Voice call. The PreflightTest
* performs a test call to Twilio and provides a
* {@link (PreflightTest:namespace).Report} object at the end. The report
* includes information about the end user's network connection (including
* jitter, packet loss, and round trip time) and connection settings.
*
* @example
* ```typescript
* const accessToken = ...;
* const preflightTest = voice.runPreflightTest(accessToken);
*
* preflightTest.on(PreflightTest.Event.Connected, () => {
* // handle when preflightTest connects
* });
*
* preflightTest.on(PreflightTest.Event.Completed, (report: PreflightTest.Report) => {
* // handle when preflightTest is complete
* });
*
* preflightTest.on(PreflightTest.Event.Failed, (error: TwilioError) => {
* // handle preflightTest errors
* });
*
* preflightTest.on(
* PreflightTest.Event.QualityWarning,
* (currentWarnings: Call.QualityWarning[], previousWarnings: Call.QualityWarning[]) => {
* // handle preflightTest quality warnings
* },
* );
*
* preflightTest.on(PreflightTest.Event.Sample, (sample: PreflightTest.Sample) => {
* // handle preflightTest sample
* });
* ```
*/
export class PreflightTest extends EventEmitter {
/**
* UUID of the PreflightTest. This is generated by the native layer and used
* to link events emitted by the native layer to the respective JS object.
*/
private _uuid: string;
/**
* PreflightTest constructor.
*
* @internal
*/
constructor(uuid: string) {
super();
this._uuid = uuid;
common.NativeEventEmitter.addListener(
Constants.ScopePreflightTest,
this._handleNativeEvent
);
// by using a setTimeout here, we let the call stack empty before we flush
// the preflight test events. this way, listeners on this object can bind
// before flushing
if (common.Platform.OS === 'ios') {
common.setTimeout(() => {
common.NativeModule.preflightTest_flushEvents();
});
}
}
/**
* Handle all PreflightTest native events.
*/
private _handleNativeEvent = (
nativePreflightTestEvent: PreflightTestType.NativeEvent
): void => {
const uuid = nativePreflightTestEvent[Constants.PreflightTestEventKeyUuid];
if (typeof uuid !== 'string') {
throw new InvalidStateError(
`Unexpected PreflightTest UUID type: "${uuid}".`
);
}
if (uuid !== this._uuid) {
return;
}
// VBLOCKS-5083
// Update this member access when we upgrade typescript for this project.
switch (nativePreflightTestEvent.preflightTestEventKeyType) {
case Constants.PreflightTestEventTypeValueCompleted: {
return this._handleCompletedEvent(nativePreflightTestEvent);
}
case Constants.PreflightTestEventTypeValueConnected: {
return this._handleConnectedEvent();
}
case Constants.PreflightTestEventTypeValueFailed: {
return this._handleFailedEvent(nativePreflightTestEvent);
}
case Constants.PreflightTestEventTypeValueQualityWarning: {
return this._handleQualityWarningEvent(nativePreflightTestEvent);
}
case Constants.PreflightTestEventTypeValueSample: {
return this._handleSampleEvent(nativePreflightTestEvent);
}
default: {
const _exhaustiveCheck: never = nativePreflightTestEvent;
throw new InvalidStateError(
`Unexpected native PreflightTest event key type: "${
(_exhaustiveCheck as any)[Constants.PreflightTestEventKeyType]
}".`
);
}
}
};
/**
* Handle completed event.
*/
private _handleCompletedEvent = (
nativeEvent: PreflightTestType.NativeEventCompleted
) => {
const report = nativeEvent[Constants.PreflightTestCompletedEventKeyReport];
if (typeof report !== 'string') {
throw constructInvalidValueError(
PreflightTest.Event.Completed,
'report',
'string',
typeof report
);
}
const parsedReport = parseReport(report);
this.emit(PreflightTest.Event.Completed, parsedReport);
};
/**
* Handle connected event.
*/
private _handleConnectedEvent = () => {
this.emit(PreflightTest.Event.Connected);
};
/**
* Handle failed event.
*/
private _handleFailedEvent = (
nativeEvent: PreflightTestType.NativeEventFailed
) => {
const { message, code } =
nativeEvent[Constants.PreflightTestFailedEventKeyError];
if (typeof message !== 'string') {
throw constructInvalidValueError(
PreflightTest.Event.Failed,
'message',
'string',
typeof message
);
}
if (typeof code !== 'number') {
throw constructInvalidValueError(
PreflightTest.Event.Failed,
'code',
'number',
typeof code
);
}
const error = constructTwilioError(message, code);
this.emit(PreflightTest.Event.Failed, error);
};
/**
* Handle quality warning event.
*/
private _handleQualityWarningEvent = (
nativeEvent: PreflightTestType.NativeEventQualityWarning
) => {
const currentWarnings =
nativeEvent[Constants.PreflightTestQualityWarningEventKeyCurrentWarnings];
if (!Array.isArray(currentWarnings)) {
throw constructInvalidValueError(
PreflightTest.Event.QualityWarning,
'currentWarnings',
'array',
typeof currentWarnings
);
}
currentWarnings.forEach((w) => {
if (typeof w !== 'string') {
throw constructInvalidValueError(
PreflightTest.Event.QualityWarning,
'element-in-currentWarnings',
'string',
typeof w
);
}
});
const previousWarnings =
nativeEvent[
Constants.PreflightTestQualityWarningEventKeyPreviousWarnings
];
if (!Array.isArray(previousWarnings)) {
throw constructInvalidValueError(
PreflightTest.Event.QualityWarning,
'previousWarnings',
'array',
typeof previousWarnings
);
}
previousWarnings.forEach((w) => {
if (typeof w !== 'string') {
throw constructInvalidValueError(
PreflightTest.Event.QualityWarning,
'element-in-previousWarnings',
'string',
typeof w
);
}
});
this.emit(
PreflightTest.Event.QualityWarning,
currentWarnings as Call.QualityWarning[],
previousWarnings as Call.QualityWarning[]
);
};
/**
* Handle sample event.
*/
private _handleSampleEvent = (
nativeEvent: PreflightTestType.NativeEventSample
) => {
const sampleStr = nativeEvent[Constants.PreflightTestSampleEventKeySample];
if (typeof sampleStr !== 'string') {
throw constructInvalidValueError(
PreflightTest.Event.Sample,
'sample',
'string',
typeof sampleStr
);
}
const sampleObj = JSON.parse(sampleStr);
this.emit(PreflightTest.Event.Sample, parseSample(sampleObj));
};
/**
* Internal helper method to invoke a native method and handle the returned
* promise from the native method.
*/
private async _invokeAndCatchNativeMethod<T>(
method: (uuid: string) => Promise<T>
) {
return method(this._uuid).catch((error: any): never => {
if (typeof error.code === 'number' && error.message)
throw constructTwilioError(error.message, error.code);
if (error.code === Constants.ErrorCodeInvalidStateError)
throw new InvalidStateError(error.message);
throw error;
});
}
/**
* Get the CallSid of the underlying Call in the PreflightTest.
*
* @returns
* Promise that
* - Resolves with a string representing the CallSid.
* - Rejects if the native layer could not find the CallSid for this
* PreflightTest object.
*/
public async getCallSid(): Promise<string> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_getCallSid
);
}
/**
* Get the end time of the PreflightTest.
*
* @returns
* A Promise that
* - Resolves with `number` if the PreflightTest has ended.
* - Resolves with `undefined` if PreflightTest has not ended.
* - Rejects if the native layer encountered an error.
*/
public async getEndTime(): Promise<number> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_getEndTime
).then(Number);
}
/**
* Get the latest stats sample generated by the PreflightTest.
*
* @returns
* A Promise that
* - Resolves with the last {@link (PreflightTest:namespace).RTCSample}
* generated by the PreflightTest.
* - Resolves with `undefined` if there is no previously generated sample.
* - Rejects if the native layer encountered an error.
*/
public async getLatestSample(): Promise<PreflightTest.RTCSample> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_getLatestSample
).then((sampleStr) => {
const sampleObj = JSON.parse(sampleStr);
return parseSample(sampleObj);
});
}
/**
* Get the final report generated by the PreflightTest.
*
* @returns
* A Promise that
* - Resolves with the final {@link (PreflightTest:namespace).Report}.
* - Resolves with `undefined` if the report is unavailable.
* - Rejects if the native layer encountered an error.
*/
public async getReport(): Promise<PreflightTest.Report> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_getReport
).then(parseReport);
}
/**
* Get the start time of the PreflightTest.
*
* @returns
* A Promise that
* - Resolves with a `number` representing the start time of the
* PreflightTest.
* - Rejects if the native layer encountered an error.
*/
public async getStartTime(): Promise<number> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_getStartTime
).then(Number);
}
/**
* Get the state of the PreflightTest.
*
* @returns
* A Promise that
* - Resolves with the current state of the PreflightTest.
* - Rejects if the native layer encountered an error.
*/
public async getState(): Promise<PreflightTest.State> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_getState
).then(parseState);
}
/**
* Stop the ongoing PreflightTest.
*
* @returns
* A Promise that
* - Resolves if the PreflightTest was successfully stopped.
* - Rejects if the native layer encountered an error.
*/
public async stop(): Promise<void> {
return this._invokeAndCatchNativeMethod(
common.NativeModule.preflightTest_stop
);
}
}
/**
* Preflight helper functions to parse JSON strings from the native layer into
* proper JS objects to emit from this class.
*/
/**
* Parse native time measurement.
*/
function parseTimeMeasurement(nativeTimeMeasurement: {
duration: number;
endTime: number;
startTime: number;
}): PreflightTest.TimeMeasurement {
return {
duration: nativeTimeMeasurement.duration,
end: nativeTimeMeasurement.endTime,
start: nativeTimeMeasurement.startTime,
};
}
/**
* Parse native call quality enum.
*/
function parseCallQuality(
nativeCallQuality: any
): PreflightTest.CallQuality | null {
switch (common.Platform.OS) {
case 'android': {
return parseCallQualityAndroid(nativeCallQuality);
}
case 'ios': {
return parseCallQualityIos(nativeCallQuality);
}
default: {
throw new InvalidStateError('Invalid platform.');
}
}
}
/**
* Parse call quality value for Android platform.
*/
function parseCallQualityAndroid(
nativeCallQuality: string | undefined | null
): PreflightTest.CallQuality | null {
if (typeof nativeCallQuality === 'undefined' || nativeCallQuality === null) {
return null;
}
if (typeof nativeCallQuality !== 'string') {
throw new InvalidStateError(
`Call quality not of type "string". Found "${typeof nativeCallQuality}".`
);
}
const parsedCallQuality = callQualityMap.android.get(nativeCallQuality);
if (typeof parsedCallQuality !== 'string') {
throw new InvalidStateError(
`Call quality invalid. Expected a string, found "${nativeCallQuality}".`
);
}
return parsedCallQuality;
}
/**
* Parse call quality for iOS platform.
*/
function parseCallQualityIos(
nativeCallQuality: number | undefined | null
): PreflightTest.CallQuality | null {
if (typeof nativeCallQuality === 'undefined' || nativeCallQuality === null) {
return null;
}
if (typeof nativeCallQuality !== 'number') {
throw new InvalidStateError(
`Call quality not of type "number". Found "${typeof nativeCallQuality}".`
);
}
const parsedCallQuality = callQualityMap.ios.get(nativeCallQuality);
if (typeof parsedCallQuality !== 'string') {
throw new InvalidStateError(
`Call quality invalid. Expected [0, 4], found "${nativeCallQuality}".`
);
}
return parsedCallQuality;
}
/**
* Parse native preflight test state value.
*/
function parseState(nativeState: string): PreflightTest.State {
const parsedState = preflightTestStateMap.get(nativeState);
if (typeof parsedState !== 'string') {
const expectedKeys = Array(preflightTestStateMap.keys()).join(', ');
throw new InvalidStateError(
'PreflightTest state invalid. ' +
`Expected one of "[${expectedKeys}]". Got "${nativeState}".`
);
}
return parsedState;
}
/**
* Parse a sample object and transform the keys to match the expected output.
*/
function parseSample(
sampleObject: Omit<
PreflightTest.RTCSample,
typeof Constants.PreflightRTCSampleTimestamp
> & { [Constants.PreflightRTCSampleTimestamp]: string }
): PreflightTest.RTCSample {
const audioInputLevel = sampleObject.audioInputLevel;
const audioOutputLevel = sampleObject.audioOutputLevel;
const bytesReceived = sampleObject.bytesReceived;
const bytesSent = sampleObject.bytesSent;
const codec = sampleObject.codec;
const jitter = sampleObject.jitter;
const mos = sampleObject.mos;
const packetsLost = sampleObject.packetsLost;
const packetsLostFraction = sampleObject.packetsLostFraction;
const packetsReceived = sampleObject.packetsReceived;
const packetsSent = sampleObject.packetsSent;
const rtt = sampleObject.rtt;
const timestamp = Number(sampleObject.timestamp);
const sample = {
audioInputLevel,
audioOutputLevel,
bytesReceived,
bytesSent,
codec,
jitter,
mos,
packetsLost,
packetsLostFraction,
packetsReceived,
packetsSent,
rtt,
timestamp,
};
return sample;
}
/**
* Parse native "isTurnRequired" value.
*/
function parseIsTurnRequired(isTurnRequired: any): boolean | null {
switch (common.Platform.OS) {
case 'android': {
return parseIsTurnRequiredAndroid(isTurnRequired);
}
case 'ios': {
return parseIsTurnRequiredIos(isTurnRequired);
}
default: {
throw new InvalidStateError('Invalid platform.');
}
}
}
/**
* Parse native "isTurnRequired" value on Android.
*/
function parseIsTurnRequiredAndroid(
isTurnRequired: boolean | undefined | null
): boolean | null {
if (typeof isTurnRequired === 'undefined' || isTurnRequired === null) {
return null;
}
if (typeof isTurnRequired !== 'boolean') {
throw new InvalidStateError(
`PreflightTest "isTurnRequired" not valid. Found "${isTurnRequired}".`
);
}
return isTurnRequired;
}
/**
* Parse native "isTurnRequired" value on iOS.
*/
function parseIsTurnRequiredIos(
isTurnRequired: string | undefined | null
): boolean | null {
if (typeof isTurnRequired === 'undefined' || isTurnRequired === null) {
return null;
}
if (typeof isTurnRequired !== 'string') {
throw new InvalidStateError(
'PreflightTest "isTurnRequired" not of type "string". ' +
`Found "${isTurnRequired}".`
);
}
const parsedValue = isTurnRequiredMap.ios.get(isTurnRequired);
if (typeof parsedValue !== 'boolean') {
throw new InvalidStateError(
`PreflightTest "isTurnRequired" not valid. Found "${isTurnRequired}".`
);
}
return parsedValue;
}
/**
* Parse native warnings array.
*/
function parseWarnings(
warnings: PreflightTest.Warning[] | undefined | null
): PreflightTest.Warning[] {
if (typeof warnings === 'undefined' || warnings === null) {
return [];
}
if (!Array.isArray(warnings)) {
throw new InvalidStateError(
`PreflightTest "warnings" invalid. Found "${warnings}".`
);
}
return warnings;
}
/**
* Parse native warningsCleared array.
*/
function parseWarningsCleared(
warningsCleared: PreflightTest.WarningCleared[] | undefined | null
): PreflightTest.WarningCleared[] {
if (typeof warningsCleared === 'undefined' || warningsCleared === null) {
return [];
}
if (!Array.isArray(warningsCleared)) {
throw new InvalidStateError(
`PreflightTest "warningsCleared" invalid. Found "${warningsCleared}".`
);
}
return warningsCleared;
}
/**
* Parse native preflight report.
*/
function parseReport(rawReport: string): PreflightTest.Report {
const unprocessedReport: any = JSON.parse(rawReport);
const callSid: string = unprocessedReport.callSid;
// Note: Android returns enum values where the first letter is capitalized.
// The helper function normalizes this into all-lowercased values.
const callQuality: PreflightTest.CallQuality | null = parseCallQuality(
unprocessedReport.callQuality
);
const edge: string = unprocessedReport.edge;
// Note: key change from `iceCandidates` to `iceCandidateStats`
const iceCandidateStats: PreflightTest.RTCIceCandidateStats[] =
unprocessedReport.iceCandidates;
// Note: iOS returns a string, Android returns a boolean
const isTurnRequired: boolean | null = parseIsTurnRequired(
unprocessedReport.isTurnRequired
);
// Note: key change from `networkStats` to `stats`.
const stats: PreflightTest.RTCStats = unprocessedReport.networkStats;
// Note: removing preflightTest from networkTiming and putting it in a
// separate testTiming member
const unprocessedNetworkTiming: {
signaling: any;
peerConnection: any;
iceConnection: any;
preflightTest: any;
} = unprocessedReport.networkTiming;
// Note: nested key change from `startTime` to `start` and `endTime` to `end`.
const networkTiming: PreflightTest.NetworkTiming = {
signaling: parseTimeMeasurement(unprocessedNetworkTiming.signaling),
peerConnection: parseTimeMeasurement(
unprocessedNetworkTiming.peerConnection
),
ice: parseTimeMeasurement(unprocessedNetworkTiming.iceConnection),
};
// Note: nested key change from `startTime` to `start` and `endTime` to `end`.
const testTiming: PreflightTest.TimeMeasurement = parseTimeMeasurement(
unprocessedNetworkTiming.preflightTest
);
// Note: key change from `statsSamples` to `stats`.
const samples: PreflightTest.RTCSample[] =
unprocessedReport.statsSamples.map(parseSample);
const selectedEdge: string = unprocessedReport.selectedEdge;
// Note: key change from `selectedIceCandidatePair` to `selectedIceCandidatePairStats`.
const selectedIceCandidatePairStats: PreflightTest.RTCSelectedIceCandidatePairStats =
unprocessedReport.selectedIceCandidatePair;
// Note: iOS returns undefined where Android returns an empty array
// when there were no warnings
const warnings: PreflightTest.Warning[] = parseWarnings(
unprocessedReport.warnings
);
// Note: iOS returns undefined where Android returns an empty array
// when there were no warningsCleared
const warningsCleared: PreflightTest.WarningCleared[] = parseWarningsCleared(
unprocessedReport.warningsCleared
);
const report: PreflightTest.Report = {
callSid,
callQuality,
edge,
iceCandidateStats,
isTurnRequired,
stats,
networkTiming,
testTiming,
samples,
selectedEdge,
selectedIceCandidatePairStats,
warnings,
warningsCleared,
};
return report;
}
/**
* Helper function to construct errors when the native layer sends an
* unexpected value to the JS layer.
*/
function constructInvalidValueError(
eventName: PreflightTest.Event,
valueName: string,
expectedType: string,
actualType: string
): InvalidStateError {
return new InvalidStateError(
`Invalid "preflightTest#${eventName}" value type for "${valueName}". ` +
`Expected "${expectedType}"; actual "${actualType}".`
);
}
/**
* Helper types for the PrefligthTest class.
*/
export namespace PreflightTest {
/**
* Options to run a PreflightTest.
*/
export interface Options {
/**
* Array of ICE servers to use for the PreflightTest.
*/
[Constants.CallOptionsKeyIceServers]?: IceServer[];
/**
* The ICE transport policy to use for the PreflightTest.
*/
[Constants.CallOptionsKeyIceTransportPolicy]?: IceTransportPolicy;
/**
* The preferred audio codec to use for the PreflightTest.
*/
[Constants.CallOptionsKeyPreferredAudioCodecs]?: AudioCodec[];
}
/**
* Events raised by the PreflightTest.
*/
export enum Event {
/** {@inheritdoc (PreflightTest:interface).(addListener:1)} */
Connected = 'connected',
/** {@inheritdoc (PreflightTest:interface).(addListener:2)} */
Completed = 'completed',
/** {@inheritdoc (PreflightTest:interface).(addListener:3)} */
Failed = 'failed',
/** {@inheritdoc (PreflightTest:interface).(addListener:4)} */
Sample = 'sample',
/** {@inheritdoc (PreflightTest:interface).(addListener:5)} */
QualityWarning = 'qualityWarning',
}
/**
* Types of the listener methods that are bound to the PreflightTest events.
*/
export namespace Listener {
/** {@inheritdoc (PreflightTest:interface).(addListener:1)} */
export type Connected = () => void;
/** {@inheritdoc (PreflightTest:interface).(addListener:2)} */
export type Completed = (report: Report) => void;
/** {@inheritdoc (PreflightTest:interface).(addListener:3)} */
export type Failed = (error: TwilioError) => void;
/** {@inheritdoc (PreflightTest:interface).(addListener:4)} */
export type Sample = (sample: RTCSample) => void;
/** {@inheritdoc (PreflightTest:interface).(addListener:5)} */
export type QualityWarning = (
currentWarnings: Call.QualityWarning[],
previousWarnings: Call.QualityWarning[]
) => void;
/** {@inheritdoc (PreflightTest:interface).(addListener:6)} */
export type Generic = (...args: any[]) => void;
}
/**
* States of the PreflightTest object.
*/
export enum State {
/**
* The state of the PreflightTest after the connected event has been raised.
*
* See {@link (PreflightTest:interface).(addListener:1)}.
*/
Connected = 'connected',
/**
* The state of the PreflightTest after the completed event has been raised.
*
* See {@link (PreflightTest:interface).(addListener:2)}.
*/
Completed = 'completed',
/**
* The state of the PreflightTest after the PreflightTest has been started
* but not yet connected.
*/
Connecting = 'connecting',
/**
* The state of the PreflightTest after the failed event has been raised.
*
* See {@link (PreflightTest:interface).(addListener:3)}.
*/
Failed = 'failed',
}
/**
* Represents general stats for a specific metric.
*/
export interface Stats {
/**
* The average value for this metric.
*/
[Constants.PreflightStatsAverage]: number;
/**
* The maximum value for this metric.
*/
[Constants.PreflightStatsMax]: number;
/**
* The minimum value for this metric.
*/
[Constants.PreflightStatsMin]: number;
}
/**
* Represents RTC related stats that are extracted from RTC samples.
*/
export interface RTCStats {
/**
* Packets delay variation.
*/
[Constants.PreflightRTCStatsJitter]: Stats;
/**
* Mean opinion score, 1.0 through roughly 4.5.
*/
[Constants.PreflightRTCStatsMos]: Stats;
/**
* Round trip time, to the server back to the client.
*/
[Constants.PreflightRTCStatsRtt]: Stats;
}
/**
* Timing measurements that provide operational milestones.
*/
export interface TimeMeasurement {
/**
* Number of milliseconds elapsed for this measurements.
*/
[Constants.PreflightTimeMeasurementDuration]: number;
/**
* A millisecond timestamp that represents the end of a PreflightTest.
*/
[Constants.PreflightTimeMeasurementEnd]: number;
/**
* A millisecond timestamp that represents the start of a PreflightTest.
*/
[Constants.PreflightTimeMeasurementStart]: number;
}
/**
* Represents network related time measurements.
*/
export interface NetworkTiming {
/**
* Measurements for establishing ICE connection.
*/
[Constants.PreflightNetworkTimingIce]: TimeMeasurement;
/**
* Measurements for establishing a PeerConnection.
*/
[Constants.PreflightNetworkTimingPeerConnection]: TimeMeasurement;
/**
* Measurements for establishing a signaling connection.
*/
[Constants.PreflightNetworkTimingSignaling]: TimeMeasurement;
}
/**
* A warning that can be raised by the `PreflightTest` and returned in the
* `PreflightTest.Report.warnings` field.
*/
export interface Warning {
/**
* Name of the warning.
*/
[Constants.PreflightWarningName]: string;
/**
* Threshold value that, when exceeded, will trigger this warning.
*/
[Constants.PreflightWarningThreshold]: string;
/**
* Detected values that exceeded the threshold value and triggered this
* warning.
*/
[Constants.PreflightWarningValues]: string;
/**
* Timestamp of the warning.
*/
[Constants.PreflightWarningTimestamp]: number;
}
/**
* Signifies when a `PreflightTest.Warning` has been cleared. Emitted by the
* `PreflightTest` when the warning was cleared and also included in the
* `PreflightTest.Report.warningsCleared` field.
*/
export interface WarningCleared {
/**
* The name of the cleared warning.
*/
[Constants.PreflightWarningClearedName]: string;
/**
* The timestamp when the warning was cleared.
*/
[Constants.PreflightWarningClearedTimestamp]: number;
}
/**
* Provides information related to the ICE candidate.
*/
export interface RTCIceCandidateStats {
/**
* The type of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsCandidateType]: string;
/**
* Whether or not the candidate was deleted.
*/
[Constants.PreflightRTCIceCandidateStatsDeleted]: boolean;
/**
* The IP address of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsIp]: string;
/**
* Whether or not the ICE candidate is remote. True if remote, false if
* local.
*/
[Constants.PreflightRTCIceCandidateStatsIsRemote]: boolean;
/**
* Represents the network cost of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsNetworkCost]: number;
/**
* The network ID of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsNetworkId]: number;
/**
* The network type of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsNetworkType]: string;
/**
* The port of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsPort]: number;
/**
* The priority of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsPriority]: number;
/**
* The protocol that the ICE candidate is using to communicate.
*/
[Constants.PreflightRTCIceCandidateStatsProtocol]: string;
/**
* The related address of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsRelatedAddress]: string;
/**
* The related port of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsRelatedPort]: number;
/**
* The TCP type of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsTcpType]: string;
/**
* The transport ID of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsTransportId]: string;
/**
* The URL of the ICE candidate.
*/
[Constants.PreflightRTCIceCandidateStatsUrl]: string;
}
/**
* The `PreflightTest.RTCIceCandidateStats` of the selected remote and local
* ICE candidates.
*/
export interface RTCSelectedIceCandidatePairStats {
/**
* The stats of the local candidate.
*/
[Constants.PreflightRTCSelectedIceCandidatePairStatsLocalCandidate]: RTCIceCandidateStats;
/**
* The stats of the remote candidate.
*/
[Constants.PreflightRTCSelectedIceCandidatePairStatsRemoteCandidate]: RTCIceCandidateStats;
}
/**
* A sample generated during the progress of a `PreflightTest`.
*/
export interface RTCSample {
/**
* The audio input level at the time when the sample was taken.
*/
[Constants.PreflightRTCSampleAudioInputLevel]: number;
/**
* The audio output level at the time when the sample was taken.
*/
[Constants.PreflightRTCSampleAudioOutputLevel]: number;
/**
* The bytes sent at the time when the sample was taken.
*/
[Constants.PreflightRTCSampleBytesReceived]: number;
/**
* The bytes received at the time when the sample was taken.
*/
[Constants.PreflightRTCSampleBytesSent]: number;
/**
* The codec used by the underlying media connection.
*/
[Constants.PreflightRTCSampleCodec]: string;
/**
* The jitter present in the underlying media connection at the time when
* the sample was taken.
*/
[Constants.PreflightRTCSampleJitter]: number;
/**
* The evaluated MOS score of the underlying media connection at the time
* when the sample was taken.
*/
[Constants.PreflightRTCSampleMos]: number;
/**
* The number of packets lost during the `PreflightTest`.
*/
[Constants.PreflightRTCSamplePacketsLost]: number;
/**
* The fraction of total packets lost during the `PreflightTest`.
*/
[Constants.PreflightRTCSamplePacketsLostFraction]: number;
/**
* The number of packets received during the `PreflightTest`.
*/
[Constants.PreflightRTCSamplePacketsReceived]: number;
/**
* The number of packets sent during the `PreflightTest`.
*/
[Constants.PreflightRTCSamplePacketsSent]: number;
/**
* The round-trip time of a network packet at the time when the sample was
* taken.
*/
[Constants.PreflightRTCSampleRtt]: number;
/**
* The timestamp of when the RTC sample was taken during the
* `PreflightTest`.
*/
[Constants.PreflightRTCSampleTimestamp]: number;
}
/**
* The call quality.
*/
export enum CallQuality {
/**
* Indicates `4.2 < average MOS`.
*/
Excellent = Constants.PreflightCallQualityExcellent,
/**
* Indicates `4.1 <= average MOS <= 4.2`.
*/
Great = Constants.PreflightCallQualityGreat,
/**
* Indicates `3.7 <= average MOS < 4.1`.
*/
Good = Constants.PreflightCallQualityGood,
/**
* Indicates `3.1 <= average MOS < 3.7`.
*/
Fair = Constants.PreflightCallQualityFair,
/**
* Indicates `average MOS < 3.1`.
*/
Degraded = Constants.PreflightCallQualityDegraded,
}
/**
* The final report generated by the `PreflightTest` upon completion. Contains
* info related to the call quality and RTC statistics generated during the
* `PreflightTest`.
*/
export interface Report {
/**
* The `CallSid` of the underlying Twilio call used by the `PreflightTest`.
*/
[Constants.PreflightReportCallSid]: string;
/**
* The rated average MOS score of the `PreflightTest`. This value can help
* indicate the expected quality of future calls.
*/
[Constants.PreflightReportCallQuality]: CallQuality | null;
/**
* The Twilio Edge used by the `Call` in the `PreflightTest`.
*/
[Constants.PreflightReportEdge]: string;
/**
* An array of ICE candidates gathered when connecting to media.
*/
[Constants.PreflightReportIceCandidateStats]: RTCIceCandidateStats[];
/**
* Whether TURN is required to connect to media.
*
* This is dependent on the selected ICE candidates, and will be `true` if
* either is of type "relay", `false` if both are of another type, or
* `null` if there are no selected ICE candidates.
*
* See `PreflightTest.Options.iceServers` for more details.
*/
[Constants.PreflightReportIsTurnRequired]: boolean | null;
/**
* The RTC related stats captured during the `PreflightTest`.
*/
[Constants.PreflightReportStats]: RTCStats;
/**
* Network related time measurements.
*/
[Constants.PreflightReportNetworkTiming]: NetworkTiming;
/**
* Time measurements of the `PreflightTest` in its entirety.
*/
[Constants.PreflightReportTestTiming]: TimeMeasurement;
/**
* RTC samples collected during the `PreflightTest`.
*/
[Constants.PreflightReportSamples]: RTCSample[];
/**
* The Twilio Edge value passed when constructing the `PreflightTest`.
*/
[Constants.PreflightReportSelectedEdge]: string;
/**
* RTC stats for the ICE candidate pair used to connect to media, if ICE
* candidates were selected.
*/
[Constants.PreflightReportSelectedIceCandidatePairStats]: RTCSelectedIceCandidatePairStats;
/**
* Array of warnings detected during the `PreflightTest`.
*/
[Constants.PreflightReportWarnings]: Warning[];
/**
* Array of warnings cleared during the `PreflightTest.`
*/
[Constants.PreflightReportWarningsCleared]: WarningCleared[];
}
}
/**
* Map of call quality values from the native layer to the expected JS values.
*/
const callQualityMap = {
ios: new Map<number, PreflightTest.CallQuality>([
[0, PreflightTest.CallQuality.Excellent],
[1, PreflightTest.CallQuality.Great],
[2, PreflightTest.CallQuality.Good],
[3, PreflightTest.CallQuality.Fair],
[4, PreflightTest.CallQuality.Degraded],
]),
android: new Map<string, PreflightTest.CallQuality>([
['Excellent', PreflightTest.CallQuality.Excellent],
['Great', PreflightTest.CallQuality.Great],
['Good', PreflightTest.CallQuality.Good],
['Fair', PreflightTest.CallQuality.Fair],
['Degraded', PreflightTest.CallQuality.Degraded],
]),
};
/**
* Map of isTurnRequired values from the native layer to the expected JS values.
*/
const isTurnRequiredMap = {
ios: new Map<string, boolean>([
['true', true],
['false', false],
]),
};
/**
* Map of state values from the native layers/common constants to the expected
* JS values.
*/
const preflightTestStateMap = new Map<string, PreflightTest.State>([
[Constants.PreflightTestStateCompleted, PreflightTest.State.Completed],
[Constants.PreflightTestStateConnected, PreflightTest.State.Connected],
[Constants.PreflightTestStateConnecting, PreflightTest.State.Connecting],
[Constants.PreflightTestStateFailed, PreflightTest.State.Failed],
]);