/** * @description 天润呼叫中心对接接口 */ import { Fn, isClient, isWorker, MaybeRefOrGetter, toRef, tryOnScopeDispose, useIntervalFn } from '@vueuse/shared/index'; import { ref, Ref, watch } from 'vue'; import { WebSocketStatus } from '@/hooks/useWebsocket'; import { ElMessage } from 'element-plus'; const DEFAULT_PING_MESSAGE = 'ping'; let UUID = '73836387-0000-0000-0000-0000-0000000000'; let _extn = ''; // 分机号 function resolveNestedOptions(options: T | true): T { if (options === true) return {} as T; return options; } export function olaFn(url: MaybeRefOrGetter, options: any) { const { onConnected, onDisconnected, onError, onMessage, immediate = true, autoClose = true, protocols = [], username = '', password = '', } = options; const data: Ref = ref(null); const status = ref('CLOSED'); const wsRef = ref(); const urlRef = toRef(url); let heartbeatPause: Fn | undefined; let heartbeatResume: Fn | undefined; let explicitlyClosed = false; let retried = 0; let bufferedData: (string | ArrayBuffer | Blob)[] = []; let pongTimeoutWait: ReturnType | undefined; const _sendBuffer = () => { if (bufferedData.length && wsRef.value && status.value === 'OPEN') { for (const buffer of bufferedData) wsRef.value.send(buffer); bufferedData = []; } }; const resetHeartbeat = () => { clearTimeout(pongTimeoutWait); pongTimeoutWait = undefined; }; // Status code 1000 -> Normal Closure https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code const close: WebSocket['close'] = (code = 1000, reason) => { if (!isClient || !wsRef.value) return; explicitlyClosed = true; resetHeartbeat(); heartbeatPause?.(); wsRef.value.close(code, reason); wsRef.value = undefined; }; const send = (data: string | ArrayBuffer | Blob | any, useBuffer = true) => { if (!wsRef.value || status.value !== 'OPEN') { if (useBuffer) bufferedData.push(data); ElMessage.error('请先签入'); return false; } _sendBuffer(); data.uuid = next_uuid(); const sedMsg = JSON.stringify(data); wsRef.value.send(sedMsg); // console.log(`${getNowDateTime()}:呼叫中心发送消息:${sedMsg}`); return true; }; const merge = (target: { [x: string]: any }, additional: { [x: string]: any; hasOwnProperty: (arg0: string) => any }) => { for (const i in additional) { if (additional.hasOwnProperty(i)) { target[i] = additional[i]; } } }; const next_uuid = () => { let u = (parseFloat(UUID.substring(29)) + 1).toString(); u = u == '2147483647' ? '0' : u; while (u.length < 12) { u = '0' + u; } UUID = '73836387-0000-0000-0000-0000-' + u; return UUID; }; const auth = (username: any, password: any) => { send({ cmd: 'auth', args: { username, password, accept: 'application/json' } }); }; const get_agent_state = (extn: any) => { send({ action: 'api', cmd: 'get_agent_state', args: { extn } }); }; const get_trunk_state = () => { send({ action: 'api', cmd: 'get_trunk_state' }); }; const login = (queue: any, extn: any, params: any) => { const args = { queue, extn }; merge(args, params); _extn = extn; return send({ action: 'api', cmd: 'login', args: args }); }; const logout = (extn?: string) => { const extns = extn || _extn; send({ action: 'api', cmd: 'logout', args: { extn: extns } }); }; const collect_dtmf = (extn: any, soundfile: any) => { send({ action: 'api', cmd: 'collect_dtmf', args: { extn, soundfile } }); }; const collect_judge = (extn: any) => { send({ action: 'api', cmd: 'collect_judge', args: { extn: extn } }); }; const ping = () => { return send({ cmd: 'ping' }, false); }; const subscribe = (k: any) => { return send({ cmd: 'subscribe', args: { key: k } }); }; const unsubscribe = (k: any) => { return send({ cmd: 'unsubscribe', args: { key: k } }); }; const go_ready = () => { send({ action: 'api', cmd: 'go_ready', args: { extn: _extn } }); }; const go_ready2 = (extn: any) => { send({ action: 'api', cmd: 'go_ready', args: { extn: extn } }); }; const go_break = (reason: any) => { send({ action: 'api', cmd: 'go_break', args: { extn: _extn, reason: reason } }); }; const go_break2 = (extn: any) => { send({ action: 'api', cmd: 'go_break', args: { extn: extn, reason: '' } }); }; const toggle_ready = () => { send({ action: 'api', cmd: 'toggle_ready', args: { extn: _extn } }); }; const answer = () => { send({ action: 'api', cmd: 'answer', args: { extn: _extn } }); }; const hangup = () => { send({ action: 'api', cmd: 'hangup_other', args: { extn: _extn } }); }; const dial = (dst: any, otherStr?: any, gateway?: any) => { send({ action: 'api', cmd: 'dial', args: { extn: _extn, dest: dst, gateway: gateway, otherStr: otherStr } }); }; const transfer = (dst: any, src?: any) => { let extn = src; if (extn == null || typeof extn == 'undefined') { extn = _extn; } send({ action: 'api', cmd: 'transfer', args: { extn: extn, dest: dst } }); }; const transfer_uuid = (uuid: any, dst: any) => { send({ action: 'api', cmd: 'transfer', args: { channel_uuid: uuid, dest: dst } }); }; const monitor = (dst: any, src: any) => { let extn = src; if (extn == null || typeof extn == 'undefined') { extn = _extn; } send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst } }); }; const monitor_uuid = (uuid: any) => { send({ action: 'api', cmd: 'monitor', args: { extn: _extn, channel_uuid: uuid } }); }; const intercept = (dst: any, src: any, gateway: any) => { let extn = src; if (extn == null || typeof extn == 'undefined') { extn = _extn; } send({ action: 'api', cmd: 'intercept', args: { extn: extn, dest: dst } }); }; const intercept_uuid = (uuid: any) => { send({ action: 'api', cmd: 'intercept', args: { extn: _extn, channel_uuid: uuid } }); }; const three_way = (dst: any, src: any, gateway?: any) => { let extn = src; if (extn == null || typeof extn == 'undefined') { extn = _extn; } send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst, three_way: 'true', goip_gateway: gateway } }); }; /* hangup the thrid party */ const exit_three_way = (ext: any) => { send({ action: 'api', cmd: 'unmonitor', args: { extn: ext, three_way: 'true' } }); }; const three_way_uuid = (uuid: any) => { send({ action: 'api', cmd: 'monitor', args: { extn: _extn, channel_uuid: uuid, three_way: 'true' } }); }; const unmonitor = (ext: any) => { send({ action: 'api', cmd: 'unmonitor', args: { extn: ext } }); }; const whisper = (w: any) => { // who = "agent" / "caller" / "both" / "none" send({ action: 'api', cmd: 'whisper', args: { extn: _extn, who: w } }); }; const consult = (dst: any) => { send({ action: 'api', cmd: 'consult', args: { extn: _extn, dest: dst } }); }; const unconsult = (dst: any) => { send({ action: 'api', cmd: 'unconsult', args: { extn: _extn, dest: dst } }); }; const consult_to_three_way = (dst: any) => { send({ action: 'api', cmd: 'consult_to_three_way', args: { extn: _extn, dest: dst } }); }; const hold = () => { send({ action: 'api', cmd: 'hold', args: { extn: _extn } }); }; const unhold = () => { send({ action: 'api', cmd: 'unhold', args: { extn: _extn } }); }; const toggle_hold = () => { send({ action: 'api', cmd: 'toggle_hold', args: { extn: _extn } }); }; const take_call = (channel_uuid: any) => { send({ action: 'api', cmd: 'take_call', args: { extn: _extn, channel_uuid: channel_uuid } }); }; const next_queue = (uuid: any) => { send({ action: 'api', cmd: 'next_queue', args: { extn: _extn, channel_uuid: uuid } }); }; const conference = (dst: any) => { send({ action: 'api', cmd: 'conference', args: { extn: _extn, dest: dst } }); }; const conference_uuid = (uuid: any) => { send({ action: 'api', cmd: 'conference', args: { extn: _extn, channel_uuid: uuid } }); }; const broadcast = (numbers: any, mute: any) => { send({ action: 'api', cmd: 'broadcast', args: { extn: _extn, numbers, mute } }); }; /* send chat to a queue or an agent to = queue send to queue to = queue.agent send to agent */ const chat = (to: any, message: any, content_type: any) => { send({ action: 'api', cmd: 'chat', args: { to: to, message: message, content_type: content_type } }); }; const message = (from: any, to: any, message: any, content_type: any) => { send({ action: 'api', cmd: 'message', args: { from: from, to: to, message: message, content_type: content_type } }); }; const alarm = (queue: any, state: any) => { send({ action: 'api', cmd: 'alarm', args: { queue, state } }); }; /* dispatching apis */ const dlogin = (ext: any) => { send({ action: 'api', cmd: 'dlogin', args: { extn: ext } }); }; const dlogout = (ext: any) => { send({ action: 'api', cmd: 'dlogout', args: { extn: ext } }); }; const inject = (ext: any, uuid: any) => { send({ action: 'api', cmd: 'inject', args: { extn: ext, channel_uuid: uuid } }); }; const kill = (uuid: any, extn: any) => { send({ action: 'api', cmd: 'kill', args: { channel_uuid: uuid, extn: extn } }); }; const eavesdrop = (uuid: any) => { send({ action: 'api', cmd: 'eavesdrop', args: { channel_uuid: uuid } }); }; const conf = (name: any, action: any, member: any) => { send({ action: 'api', cmd: 'conf', args: { name: name, action: action, member: member } }); }; const answer_all = (ext: any, queue: any) => { send({ action: 'api', cmd: 'answer_all', args: { extn: ext, queue: queue } }); }; const group_call = (ext: any, queue: any, numbers: any, batch_accept: any) => { send({ action: 'api', cmd: 'group_call', args: { extn: ext, queue: queue, numbers: numbers, batch_accept: batch_accept } }); }; const sip_gateway = (profile: any, gateway: any, op: any) => { send({ action: 'api', cmd: 'sip_gateway', args: { profile: profile, gateway: gateway, op: op } }); }; const playback = (filename: any) => { send({ action: 'api', cmd: 'playback', args: { extn: _extn, soundfile: filename } }); }; const stop_playback = () => { send({ action: 'api', cmd: 'stop_playback', args: { extn: _extn } }); }; const merge_call = (ext1: any, ext2: any) => { send({ action: 'api', cmd: 'merge_call', args: { extn1: ext1, extn2: ext2 } }); }; /* common apis*/ /*phone control api, only yealink support for now*/ const api_handfree = (ext: any) => { send({ action: 'api', cmd: 'api_handfree', args: { extn: ext } }); }; const getStatus = () => { // @ts-ignore return wsRef.value.readyState; }; const startSubscribe = () => { subscribe(`ola.agent.${options.username}`); subscribe(`ola.caller.${options.username}`); get_agent_state(options.username); }; const _init = () => { if (explicitlyClosed || typeof urlRef.value === 'undefined') return; const ws = new WebSocket(urlRef.value, protocols); wsRef.value = ws; status.value = 'CONNECTING'; ws.onopen = () => { status.value = 'OPEN'; heartbeatResume?.(); _sendBuffer(); auth(options.username, options.password); startSubscribe(); onConnected?.(ws!); }; ws.onclose = (ev) => { status.value = 'CLOSED'; onDisconnected?.(ev); if (!explicitlyClosed && options.autoReconnect) { const { retries = -1, delay = 1000, onFailed } = resolveNestedOptions(options.autoReconnect); retried += 1; // @ts-ignore if (retries < 0 || retried < retries) setTimeout(_init, delay); else if (typeof retries === 'function' && retries()) setTimeout(_init, delay); else onFailed?.(); } }; ws.onerror = (e) => { onError?.(e); }; ws.onmessage = (e: MessageEvent) => { if (options.heartbeat) { resetHeartbeat(); const { message = DEFAULT_PING_MESSAGE } = resolveNestedOptions(options.heartbeat); if (e.data === message) return; } data.value = e.data; onMessage?.(e.data); }; }; if (options.heartbeat) { const { message = DEFAULT_PING_MESSAGE, interval = 1000, pongTimeout = 1000 } = resolveNestedOptions(options.heartbeat); const { pause, resume } = useIntervalFn( () => { ping(); if (pongTimeoutWait != null) return; pongTimeoutWait = setTimeout(() => { // auto-reconnect will be trigger with ws.onclose() close(); explicitlyClosed = false; }, pongTimeout); }, interval, { immediate: false } ); heartbeatPause = pause; heartbeatResume = resume; } if (autoClose) { if (isClient) window.addEventListener('beforeunload', () => close()); tryOnScopeDispose(close); } const open = () => { if (!isClient && !isWorker) return; close(); explicitlyClosed = false; retried = 0; _init(); }; if (immediate) open(); watch(urlRef, open); return { data, status, close, send, open, ws: wsRef, login, logout, go_ready, go_break, hangup, hold, unhold, dial, exit_three_way, transfer, monitor, collect_dtmf, collect_judge, get_trunk_state, ping, unsubscribe, go_ready2, toggle_ready, answer, transfer_uuid, monitor_uuid, intercept, intercept_uuid, three_way, three_way_uuid, unmonitor, whisper, consult, unconsult, consult_to_three_way, toggle_hold, take_call, next_queue, conference, conference_uuid, broadcast, chat, message, alarm, dlogin, dlogout, inject, kill, eavesdrop, conf, answer_all, group_call, sip_gateway, playback, stop_playback, merge_call, api_handfree, getStatus, username: options.username, password: options.password, next_uuid, }; }