@kmhgmbh/dialer-vue-components
Version:
VueJS components for telephony and dialer functions 2
327 lines (242 loc) • 13.6 kB
Markdown
# Dialer Vue Components
`@kmhgmbh/dialer-vue-components` — a Vue 3 component library for telephony and dialer functions against the Vocalcom Hermes call-center system. Ships as an npm package (ES/CJS/UMD builds, `vue` externalized), UI built with Vuetify.
## Documentation
Additional documentation can be found at [Confluence](https://kmhgmbh.atlassian.net/l/cp/V0fWa8B1).
## Requirements
- Node.js 18 (see `.nvmrc`)
- Vue 3 (peer dependency)
## Installation
```bash
npm install @kmhgmbh/dialer-vue-components
```
Register the plugin with the adapter type (currently only `hermes`) and import the styles:
```ts
import { createApp } from 'vue';
import plugin from '@kmhgmbh/dialer-vue-components';
import '@kmhgmbh/dialer-vue-components/style.css';
const app = createApp(App);
app.use(plugin, { type: 'hermes' });
```
Installing the plugin does three things:
1. Globally registers the public components `TopBar`, `CallController` and `CampaignsController`.
2. Creates the telephony singleton and exposes it as `globalProperties.$telephonyAdapter` and via `provide('telephonyAdapter')`.
3. Injects the Hermes vendor scripts as `<script>` tags (in order): `/hermes/agentlink_enums.js`, `/hermes/AgentLink.js`, `/hermes/swfobject.js`, `/hermes/wsjavascript.js`.
> **Important:** The consuming app must serve the files from `public/hermes/` under the `/hermes/` path, otherwise the vendor scripts cannot be loaded and the adapter will not work.
## Usage
There are two ways to use the library: via the ready-made components (recommended) or directly via the telephony adapter.
### Components
#### `<call-controller>`
The main UI with all telephony functions (connect, call, hangup, hold, transfer, pause, recording).
```vue
<call-controller
type="hermes"
:config="config"
:credentials="{ username: 'agentId', password: 'secret', extension: '11010' }"
:campaign="{ id: 'campaignId', description: 'campaignDescription' }"
:webrtc="{ wsUrl: 'wss://…/ws', uri: 'sip:11010@…', extension: '11010' }"
:is-webrtc="true"
:is-snackbar-active="true"
:is-logging-enabled="true"
@status="onStatus"
@error="onError"
/>
```
Props:
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `String` | — (required) | Adapter type, currently `'hermes'` |
| `config` | `Object` | — (required) | Hermes connection config (see [Configuration](#configuration)) |
| `credentials` | `{ username, password, extension }` | — (required) | Login credentials |
| `campaign` | `{ id, description }` | — (required) | Manual campaign set on login |
| `isSnackbarActive` | `Boolean` | `true` | Toast notifications for errors on/off |
| `isLoggingEnabled` | `Boolean` | `true` | Adapter logging on/off |
| `isWebrtc` | `Boolean` | `false` | Audio via WebRTC (JsSIP) instead of a physical phone |
| `isOutboundUser` | `Boolean` | `false` | Outbound mode (campaign dialing, preview calls) |
| `webrtc` | `WebRtcConfig` | `undefined` | WebRTC settings: `{ wsUrl, uri, extension, logging? }` |
| `phonenumber` | `String` | `''` | Pre-filled phone number |
| `displayPhoneNumber` | `String` | `''` | Number presented to the callee (CLI) |
| `showAudioWaveform` | `Boolean` | `true` | Audio waveform visualization during the call |
Events:
| Event | Payload | Description |
| --- | --- | --- |
| `status` | `string` | Status changes (`CONNECTED`, `LOGGED_IN`, `SESSION_START`, …) |
| `error` | `string` | Error messages (also shown as snackbar if enabled) |
| `openCustomer` | `SessionInfo` | On session start in outbound mode — contains session ID, contact number, campaign |
| `loadAgentsAndServices` | — | Request from the transfer dialog to load agents/service numbers |
#### `<top-bar>`
A wrapper around `CallController` for embedding in an app header. Accepts the same props (`type`, `config`, `credentials`, `campaign`, `webrtc`, `isSnackbarActive`, `isWebrtc`, `isOutboundUser`, `isLoggingEnabled`) and forwards the `status` and `error` events.
#### `<campaigns-controller>`
UI for inbound/outbound campaigns and queues (start/stop, agent counts per queue, pause state).
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `showCallback` | `Boolean` | `false` | Show callback campaigns |
| `inboundAvailableAgentList` | `Array` | `[]` | Available agents for inbound queue auto-start |
| `updatedCampaignNames` | `Array` | `[]` | Overrides for campaign display names |
| `autoStartOutboundCampaign` | `String` | `''` | ID of an outbound campaign to start automatically |
### Direct use via the telephony adapter
The adapter is a singleton and can be accessed in three ways:
```ts
// 1. Composable (recommended)
import { useTelephony } from '@kmhgmbh/dialer-vue-components';
const telephonyAdapter = useTelephony('hermes');
// 2. provide/inject
const telephonyAdapter = inject('telephonyAdapter');
// 3. globalProperties
const telephonyAdapter = getCurrentInstance()?.appContext.config.globalProperties.$telephonyAdapter;
```
Typical flow:
```ts
telephonyAdapter.setConfig(config);
telephonyAdapter.init(true, false); // (isLoggingEnabled, isOutboundUser)
await telephonyAdapter.login('username', 'password', 'extension');
telephonyAdapter.call('+4912345678', '+4998765432');
telephonyAdapter.hangup();
telephonyAdapter.logout();
```
#### API overview
**Setup & login**
| Function | Description |
| --- | --- |
| `setConfig(config)` | Sets the connection configuration and creates the adapter (only once) |
| `init(isLoggingEnabled, isOutboundUser)` | Initializes the connection to the phone system |
| `login(username, password, extension)` | Logs the agent in (async; all parameters required) |
| `logout()` | Logs the agent out and unregisters WebRTC if needed |
**Calls**
| Function | Description |
| --- | --- |
| `call(phonenumber, displayPhonenumber)` | Starts a call; `displayPhonenumber` is the presented number |
| `hangup()` | Ends the call |
| `hold()` / `retrieve()` | Puts the call on hold / retrieves it |
| `redial(phonenumber)` | Redial |
| `previewCancel()` | Cancels a preview call (outbound) |
| `getSessionId()` / `getSession()` | Session ID / session info of the current call |
**Transfer**
| Function | Description |
| --- | --- |
| `doInternalBlindTransfer(agentId)` | Blind transfer to another agent |
| `doBlindTransfer(number, isExternal?)` | Blind transfer to a number |
| `doWarmHandover(number, isExternal?)` | Warm handover (announced transfer) |
| `handoverCall()` | Completes the handover |
**Campaigns & queues**
| Function | Description |
| --- | --- |
| `getManualCampaigns()` / `setManualCamapaign(campaign)` | Get / set manual campaigns (login required) |
| `getCampaigns()` / `getQueues()` | Available campaigns / queues |
| `startQueue(context, queue, campaignId)` / `stopQueue(…)` | Start / stop queues |
**Pause & agent state**
| Function | Description |
| --- | --- |
| `getPauseOptions()` | Available pause reasons |
| `requestPause(pauseCode)` / `stopPause()` | Request / end pause |
| `getState()` / `getAgentState()` / `getTelephonyState()` | Current adapter / agent / telephony state |
**Recording & call status**
| Function | Description |
| --- | --- |
| `startRecording(fileName)` / `stopRecording()` | Start / stop call recording (login required) |
| `canSetCallStatus()` / `setCallStatus(callStatus)` | Set call qualification (status, follow-up, comment) |
**WebRTC & audio**
| Function | Description |
| --- | --- |
| `registerWebRTC(config)` / `unregisterWebRTC()` | SIP registration via JsSIP (`{ wsUrl, uri, extension, logging? }`) |
| `toogleWebRTC(status)` | Enables/disables WebRTC for outgoing calls |
| `getAvailableMediaDevices()` | Available microphones/speakers |
| `setSelectedMicrophoneId(id)` / `setSelectedSpeakerId(id)` | Select audio devices |
| `getSelectedMicrophoneId()` / `getSelectedSpeakerId()` | Currently selected devices |
| `negotiateNewMediaStream()` | Renegotiates the media stream after a device change |
| `isMicropheAllowed()` | Checks the microphone permission |
**Reactive state** (Vue refs on the adapter): `state`, `isPause`, `pauseName`, `pauseTime`, `agentId`, `agentExtension`, `remoteAudioStream`, `localAudioStream`, `lastContactAgents`, `serviceNumbers`, `availableAgents`.
## Configuration
The `config` object for `setConfig()` / the `config` prop:
```ts
{
AgentProxy: 'hermes-proxy.example.com',
Port: 9992,
CustomerId: 1,
Locale: 'en-US',
LocalWebServiceProxy: 'WSProxy.ashx',
AdminUrl: 'http://…/hermes_net_v5/admin/',
AdminServerUrl: 'http://…/hermes_net_v5/admin/',
OnMediaWebService: 'http://…/MailService.asmx?OMS=…',
CrmUrl: 'http://…/hermes_net_v5/CRM/',
CrmServerUrl: 'http://…/hermes_net_v5/CRM/',
apiUrl: 'http://…',
}
```
### Hermes 6.3.x authentication
As of Hermes 6.3.x, a separate authentication flow is required before the AgentLink login (verification token → RSA public key → hybrid-encrypted credentials → SignIn cookies → private token). The library handles this automatically via the internal `HermesAuthService` when the config additionally contains one of the following fields:
```ts
{
// …config as above…
hermesBaseUrl: 'https://hermes.example.com', // '' = relative paths (e.g. through a dev proxy)
useHermesAuth: true, // alternatively: enable explicitly
}
```
Without these fields the adapter falls back to the legacy direct login. The actual `AgentLink.Login` is deferred via polling until the AgentLink connection is ready.
## Events
The adapter emits events you can subscribe to with `on()` (and unsubscribe with `off()`):
```ts
import { TelephonyEvents } from '@kmhgmbh/dialer-vue-components';
telephonyAdapter.on(TelephonyEvents.ERROR, (message: string) => {
setNotification(message);
});
```
| Event | Description |
| --- | --- |
| `CONNECTED` | Plugin is connected to the phone system |
| `DISCONNECTED` | Connection to the phone system lost |
| `LOGGED_IN` | Agent logged in |
| `LOGGED_OUT` | Agent logged out |
| `SESSION_START` | Call established, session created (message: `SessionInfo`) |
| `SESSION_END` | Call ended |
| `SESSION_STATE` | Session state changed (message: `{ contextType, sessionId, label }`, third argument: `SessionTelephonyStates` code) |
| `AGENT_STATE` | Global agent state changed (message: localized state label, third argument: `AgentGlobalStates` code) |
| `TELEPHONY_STATE` | Telephony context state changed (message: localized state label) |
| `RECORD_START` / `RECORD_STOP` | Call recording started / stopped |
| `CALL_TRANSFERRED` | Call was transferred (blind transfer or handover) |
| `ERROR` | Error detected |
| `CONNECTION_ERROR` | Connection or login problem detected |
| `DEBUG` | Diagnostic information (e.g. WebRTC/ICE connection issues) |
All event names are exported as the `TelephonyEvents` constant; the numeric agent state codes as `AgentGlobalStates` (`Off`, `Waiting`, `Working`, `Pause`).
## Notifications
By default the `CallController` component shows a toast notification for errors. This can be toggled via the `isSnackbarActive` prop.
## Development
### Setup
```bash
npm install
cp example.env .env # then fill in credentials/endpoints (VITE_* variables)
```
### Dev harness
`npm run dev` starts a local test app (`src/App.vue` + `TestBar.vue`) on port 8081. It is not part of the published library (the lib entry is `src/index.ts`) and reads credentials and Hermes/WebRTC endpoints from `.env`.
The Vite dev server proxies `/hermes360` to `http://localhost:8082` and rewrites the Set-Cookie headers (removes Domain/Secure, sets SameSite) so the Hermes auth cookie flow works on localhost — see [vite.config.ts](vite.config.ts).
### Commands
```bash
npm run dev # dev harness on port 8081
npm run build # vue-tsc type check + vite lib build
npm run lint # eslint
npm run lint:fix # eslint with auto-fix
npm run type-check # vue-tsc for node/vitest/app tsconfigs
npm test # vitest with coverage, single run
npm run test:unit # vitest in watch mode
npx vitest run tests/Vue.spec.ts # single test file
```
### Tests
Tests live in `tests/`, run on `happy-dom` and use the mount helpers from `tests/test-suite.ts` (`findByTestAttr()` / `findAllByTestAttr()` select elements by `data-test` attribute — use these attributes in components for testability).
### Note on the vendor scripts
The files in `public/hermes/` (`AgentLink.js`, `agentlink_enums.js`, …) are untyped vendor globals from Vocalcom. They are excluded from ESLint and test coverage and should not be refactored.
## Project structure
- `./docs` — documentation in markdown format (mkdocs)
- `./public` — public assets, including the Hermes vendor scripts (`public/hermes/`)
- `./src` — source code
- `src/index.ts` — library entry (Vue plugin)
- `src/components/` — Vue components (button components in `buttons/`)
- `src/composables/` — `telephony` (facade/singleton), `webrtc` (JsSIP), `eventBus`, `timer`, `audioWaveform`
- `src/adapters/` — `AgentLink` adapter around the Hermes vendor API
- `src/services/` — `HermesAuthService` (Hermes 6.3.x auth flow)
- `src/constants/` — event and state constants
- `./tests` — unit tests (Vitest)
## CI & publishing
Bitbucket Pipelines:
- **Pull requests:** sandworm audit, `npm run lint`, `npm test`
- **Tag `v*`:** build and automatic publish to npmjs.com
- **`main` branch:** publish step available as a manual trigger
Before releasing, bump the `version` in [package.json](package.json).