UNPKG

finesse-toolkit

Version:

useful tools for finesse phone system

263 lines (214 loc) 8.11 kB
import * as libxmljs from 'libxmljs'; import { unescape } from 'lodash'; import * as Promise from 'bluebird'; import Util from '../lib/util' const DIALOG_PREFIX = '(//Dialog|//dialog)'; const USER_PREFIX = '(//User|//user)'; export type ParsedXML = any; export interface IUserReason { code: string; label: string; } export interface IUserPayload { reason: IUserReason | null; agentState: string; lastChange: string; pendingState: string; } export interface IDialogInformation { dialogId: string; fromAddress: string; toAddress: string; type: string; start: string; lastChange: string; dialogState: string; participants: string[] } export interface IDialogPayload { dialog: boolean; dialogs?: IDialogInformation; } export interface IReasonPayload { category: string; code: string; label: string; } export default class XmlHelper { private _parsed: ParsedXML; constructor (xml) { xml = xml.replace(/\sxmlns[^"]+"[^"]+"/g, ''); // remove namespaces try { this._parsed = libxmljs.parseXml(unescape(xml)).root(); } catch (e) { this._parsed = null; console.error('Could not parse XML', e, xml); } } isUser (): boolean { return !!this._parsed && !!this._parsed.get(USER_PREFIX); } isDialog (): boolean { return !!this._parsed && !!this._parsed.get(DIALOG_PREFIX); } userJson (): IUserPayload | null { if (!this.isUser()) { return null; } let xml = this._parsed.get(USER_PREFIX); let reason = xml.get('./reasonCode'); let reasonObj = !reason ? null : { code: XmlHelper._safelyGet('id', { xml: reason })!, label: XmlHelper._safelyGet('label', { xml: reason })! }; return { reason: reasonObj, agentState: XmlHelper._safelyGet('./state', { xml, callback: Util._sanitizeState })!, lastChange: XmlHelper._safelyGet('./stateChangeTime', { xml })!, pendingState: XmlHelper._safelyGet('./pendingState', { xml })! }; } reasonsJson (): Promise<IReasonPayload[]> { return this._promisedFind('/ReasonCodes/ReasonCode', this._parsed) .then(reasons => reasons.map(reason => { let opts = { xml: reason }; let getCode = { callback: data => data.substr(data.lastIndexOf('/') + 1) }; let code = XmlHelper._safelyGet('./uri', Object.assign(opts, getCode)); return { category: XmlHelper._safelyGet('./category', opts), label: XmlHelper._safelyGet('./label', opts), code }; })); } dialogJson (extension: string): IDialogPayload { if (!this.isDialog()) return { dialog: false }; let dialogs = this._parsed.find(DIALOG_PREFIX).reduce((acc, dialog) => { let opts = { xml: dialog }; let agent = dialog.get(`./participants/Participant[mediaAddress = '${extension}']`); let participants = dialog.find(`./participants/Participant[mediaAddress != '${extension}']`); participants = participants.filter((participant) => { return XmlHelper._safelyGet('./state', { xml: participant, callback: Util._sanitizeState }) !== 'DROPPED'; }).map((participant) => { let opts = { xml: participant, callback: Util._formatPhone }; return XmlHelper._safelyGet('./mediaAddress', opts); }, []); let dialogId: string = XmlHelper._safelyGet('./id', opts) as string; let phoneNumberOpts = Object.assign({}, opts, { callback: Util._formatPhone }); acc[dialogId] = { dialogId, fromAddress: XmlHelper._safelyGet('./fromAddress', phoneNumberOpts), toAddress: XmlHelper._safelyGet('./toAddress', phoneNumberOpts), type: XmlHelper._safelyGet('./mediaProperties/callType', opts), start: XmlHelper._safelyGet('.//startTime', opts), lastChange: XmlHelper._safelyGet('./stateChangeTime', { xml: agent }), dialogState: XmlHelper._safelyGet('./state', { xml: agent, callback: Util._sanitizeState }), participants }; return acc; }, {}); return { dialog: true, dialogs }; } _filterParticipants (participants: string[]): Promise<any[]> { let formatted = participants.filter((participant) => { return XmlHelper._safelyGet('./state', { xml: participant }); }).map((filtered) => { return XmlHelper._promisedSafelyGet('./mediaAddress', { xml: filtered }); }); return Promise.all(formatted); } _promisedFind (xpath: string, root: ParsedXML): Promise<any> { return new Promise((resolve, reject) => { try { resolve(root.find(xpath)); } catch (e) { reject(`Couldn't find xpath: ${e}`); } }); } promisedDialogJson (extension): Promise<IDialogPayload> { if (!this.isDialog()) return Promise.resolve({ dialog: false }); let resolveValue = this._promisedFind(DIALOG_PREFIX, this._parsed).map((dialog: any) => { let scope: IDialogInformation = { dialogId: '', fromAddress: '', toAddress: '', type: '', start: '', lastChange: '', dialogState: '', participants: [] }; let opts = { xml: dialog }; return XmlHelper._promisedSafelyGet('./id', opts) .then((data) => { scope.dialogId = data; return this._promisedFind(`./participants/Participant[mediaAddress != '${extension}']`, dialog); }) .then(this._filterParticipants) .then((formatted) => { scope.participants = formatted; return XmlHelper._promisedSafelyGet('./fromAddress', opts); }) .then((data) => { scope.fromAddress = data; return XmlHelper._promisedSafelyGet('./toAddress', opts); }) .then((data) => { scope.toAddress = data; let type = XmlHelper._safelyGet('./mediaProperties/callType', opts); let start = XmlHelper._safelyGet('.//startTime', opts); let agent = dialog.get(`./participants/Participant[mediaAddress = '${extension}']`); let lastChange = XmlHelper._safelyGet('./stateChangeTime', { xml: agent }); let dialogState = XmlHelper._safelyGet('./state', { xml: agent, callback: Util._sanitizeState }); return { dialogId: scope.dialogId, fromAddress: scope.fromAddress, toAddress: scope.toAddress, type, start, lastChange, dialogState, participants: scope.participants }; }).catch(Util._promiseErrorHandler); }); let promisedDialogs = Promise.all(resolveValue).then((allDialogs) => { return allDialogs.reduce((acc, val) => { acc[val.dialogId] = val; return acc; }, {}); }).catch(Util._promiseErrorHandler); return promisedDialogs.then((data) => { return { dialog: true, dialogs: data }; }).catch(Util._promiseErrorHandler); } static _safelyGet (xpath: string, opts: { xml: ParsedXML; callback?: (x: string | null) => string }): string | null { let xml = opts.xml; if (!xml) return null; let node = xml.get(xpath); let returnValue = node ? node.text() : null; return (opts && opts.callback) ? opts.callback(returnValue) : returnValue; } static _promisedSafelyGet (xpath: string, opts: { xml: ParsedXML }): Promise<any> { return new Promise((resolve, reject) => { let xml = opts.xml; if (!xml) reject('No XML object'); let node = xml.get(xpath); return node ? resolve(node.text()) : reject('Node not found'); }); } // Doesn't support nested properties (yet) static objToXml (obj: { root: string; properties: Object }): string { let doc = new (libxmljs as any).Document(); let rootNode = doc.node(obj.root); Object.keys(obj.properties).map((val) => { rootNode.node(val, obj.properties[val]); }); return rootNode.toString(); } };