olaFn.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. /**
  2. * @description 天润呼叫中心对接接口
  3. */
  4. import { Fn, isClient, isWorker, MaybeRefOrGetter, toRef, tryOnScopeDispose, useIntervalFn } from '@vueuse/shared/index';
  5. import { ref, Ref, watch } from 'vue';
  6. import { WebSocketStatus } from '@/hooks/useWebsocket';
  7. import { ElMessage } from 'element-plus';
  8. const DEFAULT_PING_MESSAGE = 'ping';
  9. let UUID = '73836387-0000-0000-0000-0000-0000000000';
  10. let _extn = ''; // 分机号
  11. function resolveNestedOptions<T>(options: T | true): T {
  12. if (options === true) return {} as T;
  13. return options;
  14. }
  15. export function olaFn(url: MaybeRefOrGetter<string | URL | undefined>, options: any) {
  16. const {
  17. onConnected,
  18. onDisconnected,
  19. onError,
  20. onMessage,
  21. immediate = true,
  22. autoClose = true,
  23. protocols = [],
  24. username = '',
  25. password = '',
  26. } = options;
  27. const data: Ref<any | null> = ref(null);
  28. const status = ref<WebSocketStatus>('CLOSED');
  29. const wsRef = ref<WebSocket | undefined>();
  30. const urlRef = toRef(url);
  31. let heartbeatPause: Fn | undefined;
  32. let heartbeatResume: Fn | undefined;
  33. let explicitlyClosed = false;
  34. let retried = 0;
  35. let bufferedData: (string | ArrayBuffer | Blob)[] = [];
  36. let pongTimeoutWait: ReturnType<typeof setTimeout> | undefined;
  37. const _sendBuffer = () => {
  38. if (bufferedData.length && wsRef.value && status.value === 'OPEN') {
  39. for (const buffer of bufferedData) wsRef.value.send(buffer);
  40. bufferedData = [];
  41. }
  42. };
  43. const resetHeartbeat = () => {
  44. clearTimeout(pongTimeoutWait);
  45. pongTimeoutWait = undefined;
  46. };
  47. // Status code 1000 -> Normal Closure https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
  48. const close: WebSocket['close'] = (code = 1000, reason) => {
  49. if (!isClient || !wsRef.value) return;
  50. explicitlyClosed = true;
  51. resetHeartbeat();
  52. heartbeatPause?.();
  53. wsRef.value.close(code, reason);
  54. wsRef.value = undefined;
  55. };
  56. const send = (data: string | ArrayBuffer | Blob | any, useBuffer = true) => {
  57. if (!wsRef.value || status.value !== 'OPEN') {
  58. if (useBuffer) bufferedData.push(data);
  59. ElMessage.error('请先签入');
  60. return false;
  61. }
  62. _sendBuffer();
  63. data.uuid = next_uuid();
  64. const sedMsg = JSON.stringify(data);
  65. wsRef.value.send(sedMsg);
  66. // console.log(`${getNowDateTime()}:呼叫中心发送消息:${sedMsg}`);
  67. return true;
  68. };
  69. const merge = (target: { [x: string]: any }, additional: { [x: string]: any; hasOwnProperty: (arg0: string) => any }) => {
  70. for (const i in additional) {
  71. if (additional.hasOwnProperty(i)) {
  72. target[i] = additional[i];
  73. }
  74. }
  75. };
  76. const next_uuid = () => {
  77. let u = (parseFloat(UUID.substring(29)) + 1).toString();
  78. u = u == '2147483647' ? '0' : u;
  79. while (u.length < 12) {
  80. u = '0' + u;
  81. }
  82. UUID = '73836387-0000-0000-0000-0000-' + u;
  83. return UUID;
  84. };
  85. const auth = (username: any, password: any) => {
  86. send({ cmd: 'auth', args: { username, password, accept: 'application/json' } });
  87. };
  88. const get_agent_state = (extn: any) => {
  89. send({ action: 'api', cmd: 'get_agent_state', args: { extn } });
  90. };
  91. const get_trunk_state = () => {
  92. send({ action: 'api', cmd: 'get_trunk_state' });
  93. };
  94. const login = (queue: any, extn: any, params: any) => {
  95. const args = { queue, extn };
  96. merge(args, params);
  97. _extn = extn;
  98. return send({ action: 'api', cmd: 'login', args: args });
  99. };
  100. const logout = (extn?: string) => {
  101. const extns = extn || _extn;
  102. send({ action: 'api', cmd: 'logout', args: { extn: extns } });
  103. };
  104. const collect_dtmf = (extn: any, soundfile: any) => {
  105. send({ action: 'api', cmd: 'collect_dtmf', args: { extn, soundfile } });
  106. };
  107. const collect_judge = (extn: any) => {
  108. send({ action: 'api', cmd: 'collect_judge', args: { extn: extn } });
  109. };
  110. const ping = () => {
  111. return send({ cmd: 'ping' }, false);
  112. };
  113. const subscribe = (k: any) => {
  114. return send({ cmd: 'subscribe', args: { key: k } });
  115. };
  116. const unsubscribe = (k: any) => {
  117. return send({ cmd: 'unsubscribe', args: { key: k } });
  118. };
  119. const go_ready = () => {
  120. send({ action: 'api', cmd: 'go_ready', args: { extn: _extn } });
  121. };
  122. const go_ready2 = (extn: any) => {
  123. send({ action: 'api', cmd: 'go_ready', args: { extn: extn } });
  124. };
  125. const go_break = (reason: any) => {
  126. send({ action: 'api', cmd: 'go_break', args: { extn: _extn, reason: reason } });
  127. };
  128. const go_break2 = (extn: any) => {
  129. send({ action: 'api', cmd: 'go_break', args: { extn: extn, reason: '' } });
  130. };
  131. const toggle_ready = () => {
  132. send({ action: 'api', cmd: 'toggle_ready', args: { extn: _extn } });
  133. };
  134. const answer = () => {
  135. send({ action: 'api', cmd: 'answer', args: { extn: _extn } });
  136. };
  137. const hangup = () => {
  138. send({ action: 'api', cmd: 'hangup_other', args: { extn: _extn } });
  139. };
  140. const dial = (dst: any, otherStr?: any, gateway?: any) => {
  141. send({ action: 'api', cmd: 'dial', args: { extn: _extn, dest: dst, gateway: gateway, otherStr: otherStr } });
  142. };
  143. const transfer = (dst: any, src?: any) => {
  144. let extn = src;
  145. if (extn == null || typeof extn == 'undefined') {
  146. extn = _extn;
  147. }
  148. send({ action: 'api', cmd: 'transfer', args: { extn: extn, dest: dst } });
  149. };
  150. const transfer_uuid = (uuid: any, dst: any) => {
  151. send({ action: 'api', cmd: 'transfer', args: { channel_uuid: uuid, dest: dst } });
  152. };
  153. const monitor = (dst: any, src: any) => {
  154. let extn = src;
  155. if (extn == null || typeof extn == 'undefined') {
  156. extn = _extn;
  157. }
  158. send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst } });
  159. };
  160. const monitor_uuid = (uuid: any) => {
  161. send({ action: 'api', cmd: 'monitor', args: { extn: _extn, channel_uuid: uuid } });
  162. };
  163. const intercept = (dst: any, src: any, gateway: any) => {
  164. let extn = src;
  165. if (extn == null || typeof extn == 'undefined') {
  166. extn = _extn;
  167. }
  168. send({ action: 'api', cmd: 'intercept', args: { extn: extn, dest: dst } });
  169. };
  170. const intercept_uuid = (uuid: any) => {
  171. send({ action: 'api', cmd: 'intercept', args: { extn: _extn, channel_uuid: uuid } });
  172. };
  173. const three_way = (dst: any, src: any, gateway?: any) => {
  174. let extn = src;
  175. if (extn == null || typeof extn == 'undefined') {
  176. extn = _extn;
  177. }
  178. send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst, three_way: 'true', goip_gateway: gateway } });
  179. };
  180. /* hangup the thrid party */
  181. const exit_three_way = (ext: any) => {
  182. send({ action: 'api', cmd: 'unmonitor', args: { extn: ext, three_way: 'true' } });
  183. };
  184. const three_way_uuid = (uuid: any) => {
  185. send({ action: 'api', cmd: 'monitor', args: { extn: _extn, channel_uuid: uuid, three_way: 'true' } });
  186. };
  187. const unmonitor = (ext: any) => {
  188. send({ action: 'api', cmd: 'unmonitor', args: { extn: ext } });
  189. };
  190. const whisper = (w: any) => {
  191. // who = "agent" / "caller" / "both" / "none"
  192. send({ action: 'api', cmd: 'whisper', args: { extn: _extn, who: w } });
  193. };
  194. const consult = (dst: any) => {
  195. send({ action: 'api', cmd: 'consult', args: { extn: _extn, dest: dst } });
  196. };
  197. const unconsult = (dst: any) => {
  198. send({ action: 'api', cmd: 'unconsult', args: { extn: _extn, dest: dst } });
  199. };
  200. const consult_to_three_way = (dst: any) => {
  201. send({ action: 'api', cmd: 'consult_to_three_way', args: { extn: _extn, dest: dst } });
  202. };
  203. const hold = () => {
  204. send({ action: 'api', cmd: 'hold', args: { extn: _extn } });
  205. };
  206. const unhold = () => {
  207. send({ action: 'api', cmd: 'unhold', args: { extn: _extn } });
  208. };
  209. const toggle_hold = () => {
  210. send({ action: 'api', cmd: 'toggle_hold', args: { extn: _extn } });
  211. };
  212. const take_call = (channel_uuid: any) => {
  213. send({ action: 'api', cmd: 'take_call', args: { extn: _extn, channel_uuid: channel_uuid } });
  214. };
  215. const next_queue = (uuid: any) => {
  216. send({ action: 'api', cmd: 'next_queue', args: { extn: _extn, channel_uuid: uuid } });
  217. };
  218. const conference = (dst: any) => {
  219. send({ action: 'api', cmd: 'conference', args: { extn: _extn, dest: dst } });
  220. };
  221. const conference_uuid = (uuid: any) => {
  222. send({ action: 'api', cmd: 'conference', args: { extn: _extn, channel_uuid: uuid } });
  223. };
  224. const broadcast = (numbers: any, mute: any) => {
  225. send({ action: 'api', cmd: 'broadcast', args: { extn: _extn, numbers, mute } });
  226. };
  227. /* send chat to a queue or an agent
  228. to = queue send to queue
  229. to = queue.agent send to agent
  230. */
  231. const chat = (to: any, message: any, content_type: any) => {
  232. send({ action: 'api', cmd: 'chat', args: { to: to, message: message, content_type: content_type } });
  233. };
  234. const message = (from: any, to: any, message: any, content_type: any) => {
  235. send({ action: 'api', cmd: 'message', args: { from: from, to: to, message: message, content_type: content_type } });
  236. };
  237. const alarm = (queue: any, state: any) => {
  238. send({ action: 'api', cmd: 'alarm', args: { queue, state } });
  239. };
  240. /* dispatching apis */
  241. const dlogin = (ext: any) => {
  242. send({ action: 'api', cmd: 'dlogin', args: { extn: ext } });
  243. };
  244. const dlogout = (ext: any) => {
  245. send({ action: 'api', cmd: 'dlogout', args: { extn: ext } });
  246. };
  247. const inject = (ext: any, uuid: any) => {
  248. send({ action: 'api', cmd: 'inject', args: { extn: ext, channel_uuid: uuid } });
  249. };
  250. const kill = (uuid: any, extn: any) => {
  251. send({ action: 'api', cmd: 'kill', args: { channel_uuid: uuid, extn: extn } });
  252. };
  253. const eavesdrop = (uuid: any) => {
  254. send({ action: 'api', cmd: 'eavesdrop', args: { channel_uuid: uuid } });
  255. };
  256. const conf = (name: any, action: any, member: any) => {
  257. send({ action: 'api', cmd: 'conf', args: { name: name, action: action, member: member } });
  258. };
  259. const answer_all = (ext: any, queue: any) => {
  260. send({ action: 'api', cmd: 'answer_all', args: { extn: ext, queue: queue } });
  261. };
  262. const group_call = (ext: any, queue: any, numbers: any, batch_accept: any) => {
  263. send({ action: 'api', cmd: 'group_call', args: { extn: ext, queue: queue, numbers: numbers, batch_accept: batch_accept } });
  264. };
  265. const sip_gateway = (profile: any, gateway: any, op: any) => {
  266. send({ action: 'api', cmd: 'sip_gateway', args: { profile: profile, gateway: gateway, op: op } });
  267. };
  268. const playback = (filename: any) => {
  269. send({ action: 'api', cmd: 'playback', args: { extn: _extn, soundfile: filename } });
  270. };
  271. const stop_playback = () => {
  272. send({ action: 'api', cmd: 'stop_playback', args: { extn: _extn } });
  273. };
  274. const merge_call = (ext1: any, ext2: any) => {
  275. send({ action: 'api', cmd: 'merge_call', args: { extn1: ext1, extn2: ext2 } });
  276. };
  277. /* common apis*/
  278. /*phone control api, only yealink support for now*/
  279. const api_handfree = (ext: any) => {
  280. send({ action: 'api', cmd: 'api_handfree', args: { extn: ext } });
  281. };
  282. const getStatus = () => {
  283. // @ts-ignore
  284. return wsRef.value.readyState;
  285. };
  286. const startSubscribe = () => {
  287. subscribe(`ola.agent.${options.username}`);
  288. subscribe(`ola.caller.${options.username}`);
  289. get_agent_state(options.username);
  290. };
  291. const _init = () => {
  292. if (explicitlyClosed || typeof urlRef.value === 'undefined') return;
  293. const ws = new WebSocket(urlRef.value, protocols);
  294. wsRef.value = ws;
  295. status.value = 'CONNECTING';
  296. ws.onopen = () => {
  297. status.value = 'OPEN';
  298. heartbeatResume?.();
  299. _sendBuffer();
  300. auth(options.username, options.password);
  301. startSubscribe();
  302. onConnected?.(ws!);
  303. };
  304. ws.onclose = (ev) => {
  305. status.value = 'CLOSED';
  306. onDisconnected?.(ev);
  307. if (!explicitlyClosed && options.autoReconnect) {
  308. const { retries = -1, delay = 1000, onFailed } = resolveNestedOptions(options.autoReconnect);
  309. retried += 1;
  310. // @ts-ignore
  311. if (retries < 0 || retried < retries) setTimeout(_init, delay);
  312. else if (typeof retries === 'function' && retries()) setTimeout(_init, delay);
  313. else onFailed?.();
  314. }
  315. };
  316. ws.onerror = (e) => {
  317. onError?.(e);
  318. };
  319. ws.onmessage = (e: MessageEvent) => {
  320. if (options.heartbeat) {
  321. resetHeartbeat();
  322. const { message = DEFAULT_PING_MESSAGE } = resolveNestedOptions(options.heartbeat);
  323. if (e.data === message) return;
  324. }
  325. data.value = e.data;
  326. onMessage?.(e.data);
  327. };
  328. };
  329. if (options.heartbeat) {
  330. const { message = DEFAULT_PING_MESSAGE, interval = 1000, pongTimeout = 1000 } = resolveNestedOptions(options.heartbeat);
  331. const { pause, resume } = useIntervalFn(
  332. () => {
  333. ping();
  334. if (pongTimeoutWait != null) return;
  335. pongTimeoutWait = setTimeout(() => {
  336. // auto-reconnect will be trigger with ws.onclose()
  337. close();
  338. explicitlyClosed = false;
  339. }, pongTimeout);
  340. },
  341. interval,
  342. { immediate: false }
  343. );
  344. heartbeatPause = pause;
  345. heartbeatResume = resume;
  346. }
  347. if (autoClose) {
  348. if (isClient) window.addEventListener('beforeunload', () => close());
  349. tryOnScopeDispose(close);
  350. }
  351. const open = () => {
  352. if (!isClient && !isWorker) return;
  353. close();
  354. explicitlyClosed = false;
  355. retried = 0;
  356. _init();
  357. };
  358. if (immediate) open();
  359. watch(urlRef, open);
  360. return {
  361. data,
  362. status,
  363. close,
  364. send,
  365. open,
  366. ws: wsRef,
  367. login,
  368. logout,
  369. go_ready,
  370. go_break,
  371. hangup,
  372. hold,
  373. unhold,
  374. dial,
  375. exit_three_way,
  376. transfer,
  377. monitor,
  378. collect_dtmf,
  379. collect_judge,
  380. get_trunk_state,
  381. ping,
  382. unsubscribe,
  383. go_ready2,
  384. toggle_ready,
  385. answer,
  386. transfer_uuid,
  387. monitor_uuid,
  388. intercept,
  389. intercept_uuid,
  390. three_way,
  391. three_way_uuid,
  392. unmonitor,
  393. whisper,
  394. consult,
  395. unconsult,
  396. consult_to_three_way,
  397. toggle_hold,
  398. take_call,
  399. next_queue,
  400. conference,
  401. conference_uuid,
  402. broadcast,
  403. chat,
  404. message,
  405. alarm,
  406. dlogin,
  407. dlogout,
  408. inject,
  409. kill,
  410. eavesdrop,
  411. conf,
  412. answer_all,
  413. group_call,
  414. sip_gateway,
  415. playback,
  416. stop_playback,
  417. merge_call,
  418. api_handfree,
  419. getStatus,
  420. username: options.username,
  421. password: options.password,
  422. next_uuid,
  423. };
  424. }