purecloudlabs / softphone-vendor-headsets

Softphone Vendor Headsets Repo
MIT License
9 stars 11 forks source link

Softphone Vendor Headsets

Overview

This library's goal is to abstract all the different headset implementations behind a single interface.

This project has a React test app bootstrapped with create-react-app.

At this moment (12/10/2021) there are three supported vendors already handled with this library:

As of 11/3/2022 we now support:

As of 2/12/24 we now support:

Installation

# npm
npm install --save softphone-vendor-headsets
# yarn
yarn add softphone-vendor-headsets

Documentation

Headset Service API



getInstance

This will create a new instance of the headset service if one doesn't already exist. It's important to note the original instance will always be returned, even if you pass in different config options so make sure you get it right the first time. There's only one relevant option for the headset config, see below.

static getInstance(config: ImplementationConfig);

interface ImplementationConfig {
  logger: any;
}




implementations

This is a computed value that returns a list of selectable headset vendors based on browser/environment compatibility. It does not ensure the required 3rd party software is installed. That gets checked when a vendor is selected or changed.


activeMicChange

The selected headset vendor is determined by the active mic. This method notifies headset service the active mic has changed and it should determine if the "new" mic has an associated vendor implementation.

activeMicChange (newMicLabel: string): void;

Params:

changeImplementation

Allows you to manually change the selected headset. This is an alternative to activeMicChange(...).

changeImplementation (implementation: VendorImplementation | null, deviceLabel: string): Promise<void>

params:

incomingCall

Notifies the headset you have an incoming call. In most cases this will result in the headset ringing.

incomingCall (callInfo: CallInfo, hasOtherActiveCalls?: boolean): Promise<any>

params:

outgoingCall

Notifies the headset you are placing an outgoing call.

outgoingCall (callInfo: CallInfo): Promise<any>

params:

answerCall

Notifies the headset you are answering an incoming call. This will end the ringing and enable headset controls.

answerCall (conversationId: string): Promise<any>

params:

rejectCall

Notifies the headset you are rejecting an incoming call. This will end the ringing.

rejectCall (conversationId: string): Promise<any>

params:

setMute

Tells the headset to mute or unmute.

setMute (value: boolean): Promise<any>

params:

setHold

Tells the headset you are holding or resuming a call.

setHold (conversationId: string, value: boolean): Promise<any>

params:

connectionStatus

Returns the current connection state of the headset.

connectionStatus (): DeviceConnectionStatus

Returns:

  type DeviceConnectionStatus = 'checking' | 'running' | 'notRunning' | 'noVendor';




Headset Events

The Headset service does not explicitly emit events itself. It uses RxJS observables to emit the events which are then subscribed to within the consuming app.

deviceAnsweredCall

Event emitted when a user presses the answer call button during an incoming call on their selected device. The event includes the event name as it is interpretted by the headset and a collection of items that may help with logging (event). It can also potentially have a code that corresponds to the event.

Declaration:

    headset.headsetEvents.subscribe(event: {
        event: 'deviceAnsweredCall',
        payload: {
            name: string,
            code?: string,
            event: { `containing various items mostly for logging purposes` }
        }
    } => {
        if (event.event === 'deviceAnsweredCall') {
            sdk.acceptPendingSession();
        }
    })

Value of event:



deviceEndedCall

Event emitted when a user presses the answer call button while in an active call on their selected device. The event includes the event name as it is interpretted by the headset and a collection of items that may help with logging (event). It can also potentially have a code that corresponds to the event.

Declaration:

    headset.headsetEvents.subscribe(event: {
        event: 'deviceEndedCall',
        payload: {
            name: string,
            event: { `containing various items mostly for logging purposes` },
            code?: string
        } => {
            if (event.event === 'deviceEndedCall') {
                sdk.endSession({ conversationId });
            }
        }
    })

Value of event:

deviceMuteStatusChanged

Event emitted when a user presses the mute call button on their selected device. It doesn't matter if the device state is currently muted or unmuted, this event will be emitted with the OPPOSITE value. For example, if the headset is currently muted, it will emit the event with the corresponding value to unmute the device. The event includes the event name as it is interpretted by the headset and a collection of items that may help with logging (event). It also comes with a value known as isMuted which determines the event is trying to mute or unmute the call. It can also potentially have a code that corresponds to the event.

Declaration:

    headset.headsetEvents.subscribe(event: {
        event: 'deviceMuteStatusChanged',
        payload: {
            name: string,
            event: { `containing various items mostly for logging purposes` },
            isMuted: boolean,
            code?: string
        } => {
            if (event.event === 'deviceMuteStatusChanged') {
                sdk.setAudioMute(event.payload.isMuted);
            }
        }
    })

Value of event:

deviceHoldStatusChanged

Event emitted when a user presses the hold call button on their selected device. It doesn't matter if the device state is currently on hold or not, this event will be emitted with the OPPOSITE value. For example, if the headset is currently on hold, it will emit the event with the corresponding value to resume the call. The event includes the event name as it is interpretted by the headset and a collection of items that may help with logging (event). It also comes with a value known as holdRequested which determines the event is trying to hold or resume the call. It will also have an optional value for toggle. It can also potentially have a code that corresponds to the event.

Declaration:

    headset.headsetEvents.subscribe(event: {
        event: 'deviceHoldStatusChanged',
        payload: {
            name: string,
            event: { `containing various items mostly for logging purposes` },
            holdRequested: boolean,
            code?: string
        } => {
            if (event.event === 'deviceHoldStatusChanged') {
                sdk.setConversationHold(event.payload.holdRequested, event.payload.toggle);
            }
        }
    })

Value of event:

webHidPermissionRequested

This is a special event that is only necessary for specific devices. Certain devices (such as Jabra) support a technology known as WebHID that requires additional permissions in order to use the call controls. This event is emitted when a WebHID enabled device is selected. The event includes a callback function that is required in order to achieve additional permissions for WebHID

Declaration:

    headset.headsetEvents.subscribe(event: {
        event: 'webHidPermissionRequested',
        payload: {
            callback: Function
        } => {
            if (event.event === 'webHidPermissionRequested') {
                event.payload.body.callback();
                /* Please note: The above example will not work as is. You can't trigger the WebHID callback by simply calling, it must be triggered through user interaction such as clicking a button */
            }
        }
    })

Value of event:

Declaration:

    headset.headsetEvents.subscribe(event: {
        event: 'deviceConnectionStatusChanged',
        payload: {
            isConnected: boolean,
            isConnecting: boolean
        } => {
            if (event.event === 'deviceConnectionStatusChanged') {
                correspondingFunctionToHandleConnectionChange({event.payload.isConnected, event.payload.isConnecting});
            }
        }
    })

Value of event:

Structure and flow

Example 1 - User clicks mute in the consuming app:

Example 2 - User presses the mute button from the headset:

WebHID

One of our supported vendors has began working with a technology known as WebHID. This is a relatively newer technology with a lot of promise but with its own caveats as well - https://wicg.github.io/webhid/

To get started in development:

npm install
cd react-app
yarn start

Then navigate to https://localhost:8443 to see the test app. This way you can see the effects of the events from the headset on the app and vice versa.

Testing

Run the tests using npm run test:watch or npm run test:coverage. Both commands should be run in the folder.

All linting and tests must pass 100% and coverage should remain at 100%

Important Note: Out of the box, the test scripts will not work on Windows machines. A developer will more than likely need to make modifications to the scripts in the package.json as well as the shell scripts found in the scripts folder. If you do not want to modify the scripts out of the box, using a Linux instance seemed to help. The author of the library used an Ubuntu instance