@botonic/core
Version:
Build Chatbots using React
372 lines (334 loc) • 8.97 kB
text/typescript
import axios from 'axios'
import { PATH_PAYLOAD_IDENTIFIER } from './constants'
import {
BotonicAction,
BotonicActionType,
EVENT_FORMAT_VERSION,
Session,
} from './models'
const HUBTYPE_API_URL = 'https://api.hubtype.com'
export interface HubtypeAgentsInfo {
attending_count: number
email: string
idle_count: number
last_message_sent: string
status: string
}
export interface BackendContext {
timeoutMs: number
}
export interface VacationRange {
end_date: number // timestamp
id: number
start_date: number // timestamp
}
export type HandoffExtraData = {
language?: string
url?: string
location?: string
}
export interface BotEventData {
flowId: string
flowName: string
flowNodeId: string
flowNodeContentId: string
}
export interface FormattedBotEventData {
format_version: number
flow_id: string
flow_name: string
flow_node_id: string
flow_node_content_id: string
}
export enum HelpdeskEvent {
StatusChanged = 'status_changed',
AgentMessageCreated = 'agent_message_created',
QueuePositionChanged = 'queue_position_changed',
}
function contextDefaults(context: any): BackendContext {
return {
timeoutMs: context.timeoutMs || 10000,
}
}
export async function getOpenQueues(
session: Session,
context = {}
): Promise<{ queues: string[] }> {
//be aware of https://github.com/axios/axios/issues/1543
const baseUrl = session._hubtype_api || HUBTYPE_API_URL
const endpointUrl = `${baseUrl}/v1/queues/get_open_queues/`
context = contextDefaults(context)
const resp = await axios({
headers: {
Authorization: `Bearer ${session._access_token}`,
},
method: 'post',
url: endpointUrl,
data: { bot_id: session.bot.id },
timeout: (context as BackendContext).timeoutMs,
})
return resp.data
}
export class HandOffBuilder {
_session: Session
_queue: string
_onFinish: string
_email: string
_agentId: string
_forceAssignIfNotAvailable: boolean
_autoAssignOnWaiting: boolean
_note: string
_caseInfo: string
_autoIdleMessage: string
_shadowing: boolean
_extraData: HandoffExtraData
_bot_event: FormattedBotEventData
_subscribeHelpdeskEvents: HelpdeskEvent[]
constructor(session: Session) {
this._session = session
}
withQueue(queueNameOrId: string): this {
this._queue = queueNameOrId
return this
}
withOnFinishPayload(payload: string): this {
this._onFinish = payload
return this
}
withOnFinishPath(path: string): this {
this._onFinish = `${PATH_PAYLOAD_IDENTIFIER}${path}`
return this
}
withAgentEmail(email: string): this {
this._email = email
return this
}
withAgentId(agentId: string): this {
this._agentId = agentId
return this
}
withForceAssignIfNotAvailable(forceAssign: boolean): this {
this._forceAssignIfNotAvailable = forceAssign
return this
}
withAutoAssignOnWaiting(autoAssignOnWaiting: boolean): this {
this._autoAssignOnWaiting = autoAssignOnWaiting
return this
}
withNote(note: string): this {
this._note = note
return this
}
withCaseInfo(caseInfo: string): this {
this._caseInfo = caseInfo
return this
}
withAutoIdleMessage(message: string): this {
this._autoIdleMessage = message
return this
}
withShadowing(shadowing = true): this {
this._shadowing = shadowing
return this
}
withExtraData(extraData: HandoffExtraData): this {
this._extraData = extraData
return this
}
withBotEvent(botEvent: BotEventData): this {
this._bot_event = {
format_version: EVENT_FORMAT_VERSION,
flow_id: botEvent.flowId,
flow_name: botEvent.flowName,
flow_node_id: botEvent.flowNodeId,
flow_node_content_id: botEvent.flowNodeContentId,
}
return this
}
withSubscribeHelpdeskEvents(events: HelpdeskEvent[]): this {
this._subscribeHelpdeskEvents = events
return this
}
async handOff(): Promise<void> {
return _humanHandOff(
this._session,
this._queue,
this._onFinish,
this._email,
this._agentId,
this._forceAssignIfNotAvailable,
this._autoAssignOnWaiting,
this._caseInfo,
this._note,
this._autoIdleMessage,
this._shadowing,
this._extraData,
this._bot_event,
this._subscribeHelpdeskEvents
)
}
}
/**
* @deprecated use {@link HandOffBuilder} class instead
*/
export async function humanHandOff(session, queueNameOrId = '', onFinish) {
const builder = new HandOffBuilder(session)
if (queueNameOrId) {
builder.withQueue(queueNameOrId)
}
if (onFinish) {
if (onFinish.path) {
builder.withOnFinishPath(onFinish.path)
} else if (onFinish.payload) {
builder.withOnFinishPayload(onFinish.payload)
} else {
throw new Error('onFinish requires payload or path field')
}
}
return builder.handOff()
}
export interface HubtypeHandoffParams {
queue?: string
agent_email?: string
agent_id?: string
force_assign_if_not_available?: boolean
auto_assign_on_waiting?: boolean
case_info?: string
note?: string
auto_idle_message?: string
shadowing?: boolean
on_finish?: string
case_extra_data?: HandoffExtraData
bot_event?: FormattedBotEventData
subscribe_helpdesk_events?: HelpdeskEvent[]
}
async function _humanHandOff(
session: Session,
queueNameOrId = '',
onFinish: string,
agentEmail = '',
agentId = '',
forceAssignIfNotAvailable = true,
autoAssignOnWaiting = false,
caseInfo = '',
note = '',
autoIdleMessage = '',
shadowing = false,
extraData: HandoffExtraData | undefined = undefined,
botEvent: FormattedBotEventData,
subscribeHelpdeskEvents: HelpdeskEvent[] = []
) {
const params: HubtypeHandoffParams = {}
params.force_assign_if_not_available = forceAssignIfNotAvailable
if (queueNameOrId) {
params.queue = queueNameOrId
}
if (agentEmail) {
params.agent_email = agentEmail
}
if (agentId) {
params.agent_id = agentId
}
if (autoAssignOnWaiting) {
params.auto_assign_on_waiting = autoAssignOnWaiting
}
if (caseInfo) {
params.case_info = caseInfo
}
if (note) {
params.note = note
}
if (autoIdleMessage) {
params.auto_idle_message = autoIdleMessage
}
if (shadowing) {
params.shadowing = shadowing
}
if (onFinish) {
params.on_finish = onFinish
}
if (extraData) {
params.case_extra_data = extraData
}
if (botEvent) {
params.bot_event = botEvent
}
if (subscribeHelpdeskEvents.length > 0) {
params.subscribe_helpdesk_events = subscribeHelpdeskEvents
}
session._botonic_action = `${BotonicAction.CreateCase}:${JSON.stringify(params)}`
}
export async function storeCaseRating(
session: Session,
rating: number,
context: any = {}
): Promise<{ status: string }> {
const baseUrl = session._hubtype_api || HUBTYPE_API_URL
const chatId = session.user.id
context = contextDefaults(context)
const resp = await axios({
headers: {
Authorization: `Bearer ${session._access_token}`,
},
method: 'post',
url: `${baseUrl}/v1/chats/${chatId}/store_case_rating/`,
data: { chat_id: chatId, rating },
timeout: (context as BackendContext).timeoutMs,
})
return resp.data
}
export async function getAvailableAgentsByQueue(
session: Session,
queueId: string
): Promise<{ agents: string[] }> {
const baseUrl = session._hubtype_api || HUBTYPE_API_URL
const resp = await axios({
headers: {
Authorization: `Bearer ${session._access_token}`,
},
method: 'post',
url: `${baseUrl}/v1/queues/${queueId}/get_available_agents/`,
})
return resp.data
}
export async function getAvailableAgents(
session: Session
): Promise<{ agents: HubtypeAgentsInfo[] }> {
const baseUrl = session._hubtype_api || HUBTYPE_API_URL
const botId = session.bot.id
const resp = await axios({
headers: {
Authorization: `Bearer ${session._access_token}`,
},
method: 'post',
url: `${baseUrl}/v1/bots/${botId}/get_agents/`,
})
return resp.data
}
export async function getAgentVacationRanges(
session: Session,
{ agentId, agentEmail }: { agentId?: string; agentEmail?: string }
): Promise<{ vacation_ranges: VacationRange[] }> {
const baseUrl = session._hubtype_api || HUBTYPE_API_URL
const botId = session.bot.id
const resp = await axios({
headers: {
Authorization: `Bearer ${session._access_token}`,
},
method: 'get',
url: `${baseUrl}/v1/bots/${botId}/get_agent_vacation_ranges/`,
params: { agent_id: agentId, agent_email: agentEmail },
})
return resp.data
}
export function cancelHandoff(
session: Session,
typification: string | null = null
): void {
let action: BotonicActionType = BotonicAction.DiscardCase
if (typification) action = `${action}:${JSON.stringify({ typification })}`
session._botonic_action = action
}
export function deleteUser(session: Session): void {
session._botonic_action = BotonicAction.DeleteUser
}