finesse-toolkit
Version:
useful tools for finesse phone system
263 lines (214 loc) • 8.11 kB
text/typescript
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();
}
};