12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019 |
- import logger from './logger';
- import {
- ACTION_HOOK_TYPE_NAME,
- COMMANDS,
- EVENT_TYPE,
- HOOK_STATUS,
- IDeviceInfo,
- INPUT_REPORT_EVENT_NAME
- } from './types';
- import {
- DEVICE_USAGE,
- TELEPHONY_DEVICE_USAGE_PAGE,
- requestTelephonyHID
- } from './utils';
-
- /**
- * WebHID manager that incorporates all hid specific logic.
- *
- * @class WebHidManager
- */
- export default class WebHidManager extends EventTarget {
- hidSupport: boolean;
- deviceInfo: IDeviceInfo;
- availableDevices: HIDDevice[];
- isParseDescriptorsSuccess: boolean;
- outputEventGenerators: { [key: string]: Function; };
- deviceCommand = {
- outputReport: {
- mute: {
- reportId: 0,
- usageOffset: -1
- },
- offHook: {
- reportId: 0,
- usageOffset: -1
- },
- ring: {
- reportId: 0,
- usageOffset: 0
- },
- hold: {
- reportId: 0,
- usageOffset: 0
- }
- },
- inputReport: {
- hookSwitch: {
- reportId: 0,
- usageOffset: -1,
- isAbsolute: false
- },
- phoneMute: {
- reportId: 0,
- usageOffset: -1,
- isAbsolute: false
- }
- }
- };
-
- private static instance: WebHidManager;
-
- /**
- * WebHidManager getInstance.
- *
- * @static
- * @returns {WebHidManager} - WebHidManager instance.
- */
- static getInstance(): WebHidManager {
- if (!this.instance) {
- this.instance = new WebHidManager();
- }
-
- return this.instance;
- }
-
- /**
- * Creates an instance of WebHidManager.
- *
- */
- constructor() {
- super();
-
- this.deviceInfo = {} as IDeviceInfo;
- this.hidSupport = this.isSupported();
- this.availableDevices = [];
- this.isParseDescriptorsSuccess = false;
- this.outputEventGenerators = {};
- }
-
- /**
- * Check support of hid in navigator.
- * - experimental API in Chrome.
- *
- * @returns {boolean} - True if supported, otherwise false.
- */
- isSupported(): boolean {
- // @ts-ignore
- return Boolean(window.navigator.hid?.requestDevice);
- }
-
- /**
- * Handler for requesting telephony hid devices.
- *
- * @returns {HIDDevice[]|null}
- */
- async requestHidDevices() {
- if (!this.hidSupport) {
- logger.warn('The WebHID API is NOT supported!');
-
- return null;
- }
-
- if (this.deviceInfo?.device && this.deviceInfo.device.opened) {
- await this.close();
- }
-
- // @ts-ignore
- const devices = await navigator.hid.requestDevice(requestTelephonyHID);
-
- if (!devices?.length) {
- logger.warn('No HID devices selected.');
-
- return false;
- }
-
- this.availableDevices = devices;
-
- return devices;
- }
-
- /**
- * Handler for listen to already connected hid.
- *
- * @returns {void}
- */
- async listenToConnectedHid() {
- const devices = await this.loadPairedDevices();
-
- if (!devices?.length) {
- logger.warn('No hid device found.');
-
- return;
- }
-
- const telephonyDevice = this.getTelephonyDevice(devices);
-
- if (!telephonyDevice) {
- logger.warn('No HID device to request');
-
- return;
- }
-
- await this.open(telephonyDevice);
-
- // restore the default state of hook and mic LED
- this.resetDeviceState();
-
- // switch headsets to OFF_HOOK for mute/unmute commands
- this.sendDeviceReport({ command: COMMANDS.OFF_HOOK });
- }
-
- /**
- * Get first telephony device from availableDevices.
- *
- * @param {HIDDevice[]} availableDevices -.
- * @returns {HIDDevice} -.
- */
- private getTelephonyDevice(availableDevices: HIDDevice[]) {
- if (!availableDevices?.length) {
- logger.warn('No HID device to request');
-
- return undefined;
- }
-
- return availableDevices?.find(device => this.findTelephonyCollectionInfo(device.collections));
- }
-
- /**
- * Find telephony collection info from a list of collection infos.
- *
- * @private
- * @param {HIDCollectionInfo[]} deviceCollections -.
- * @returns {HIDCollectionInfo} - Hid collection info.
- */
- private findTelephonyCollectionInfo(deviceCollections: HIDCollectionInfo[]) {
- return deviceCollections?.find(
- (collection: HIDCollectionInfo) => collection.usagePage === TELEPHONY_DEVICE_USAGE_PAGE
- );
- }
-
- /**
- * Open the hid device and start listening to inputReport events.
- *
- * @param {HIDDevice} telephonyDevice -.
- * @returns {void} -.
- */
- private async open(telephonyDevice: HIDDevice) {
- try {
- this.deviceInfo = { device: telephonyDevice } as IDeviceInfo;
-
- if (!this.deviceInfo?.device) {
- logger.warn('no HID device found');
-
- return;
- }
-
- if (!this.deviceInfo.device.opened) {
- await this.deviceInfo.device.open();
- }
-
- this.isParseDescriptorsSuccess = await this.parseDeviceDescriptors(this.deviceInfo.device);
-
- if (!this.isParseDescriptorsSuccess) {
- logger.warn('Failed to parse webhid');
-
- return;
- }
-
- this.dispatchEvent(new CustomEvent(EVENT_TYPE.INIT_DEVICE, { detail: {
- deviceInfo: {
- ...this.deviceInfo
- } as IDeviceInfo } }));
-
- // listen for input reports by registering an oninputreport event listener
- this.deviceInfo.device.oninputreport = await this.handleInputReport.bind(this);
-
- this.resetDeviceState();
- } catch (e) {
- logger.error(`Error content open device:${e}`);
- }
- }
-
- /**
- * Close device and reset state.
- *
- * @returns {void}.
- */
- async close() {
- try {
- await this.resetDeviceState();
-
- if (this.availableDevices) {
- logger.info('clear available devices list');
- this.availableDevices = [];
- }
-
- if (!this.deviceInfo) {
- return;
- }
-
- if (this.deviceInfo?.device?.opened) {
- await this.deviceInfo.device.close();
- }
-
- if (this.deviceInfo.device) {
- this.deviceInfo.device.oninputreport = null;
- }
- this.deviceInfo = {} as IDeviceInfo;
- } catch (e) {
- logger.error(e);
- }
- }
-
- /**
- * Get paired hid devices.
- *
- * @returns {HIDDevice[]}
- */
- async loadPairedDevices() {
- try {
- // @ts-ignore
- const devices = await navigator.hid.getDevices();
-
- this.availableDevices = devices;
-
- return devices;
- } catch (e) {
- logger.error('loadPairedDevices error:', e);
- }
- }
-
- /**
- * Parse device descriptors - input and output reports.
- *
- * @param {HIDDevice} device -.
- * @returns {boolean} - True if descriptors have been parsed with success.
- */
- parseDeviceDescriptors(device: HIDDevice) {
- try {
- this.outputEventGenerators = {};
-
- if (!device?.collections) {
- logger.error('Undefined device collection');
-
- return false;
- }
-
- const telephonyCollection = this.findTelephonyCollectionInfo(device.collections);
-
- if (!telephonyCollection || Object.keys(telephonyCollection).length === 0) {
- logger.error('No telephony collection');
-
- return false;
- }
-
- if (telephonyCollection.inputReports) {
- if (!this.parseInputReports(telephonyCollection.inputReports)) {
- logger.warn('parse inputReports failed');
-
- return false;
- }
- logger.warn('parse inputReports success');
-
- }
-
- if (telephonyCollection.outputReports) {
- if (!this.parseOutputReports(telephonyCollection.outputReports)) {
- logger.warn('parse outputReports failed');
-
- return false;
- }
- logger.warn('parse outputReports success');
-
- return true;
-
- }
-
- logger.warn('parseDeviceDescriptors: returns false, end');
-
- return false;
- } catch (e) {
- logger.error(`parseDeviceDescriptors error:${JSON.stringify(e, null, ' ')}`);
-
- return false;
- }
- }
-
- /**
- * HandleInputReport.
- *
- * @param {HIDInputReportEvent} event -.
- * @returns {void} -.
- */
- handleInputReport(event: HIDInputReportEvent) {
- try {
- const { data, device, reportId } = event;
-
- if (reportId === 0) {
- logger.warn('handleInputReport: ignore invalid reportId');
-
- return;
- }
-
- const inputReport = this.deviceCommand.inputReport;
-
- logger.warn(`current inputReport:${JSON.stringify(inputReport, null, ' ')}, reporId: ${reportId}`);
- if (reportId !== inputReport.hookSwitch.reportId && reportId !== inputReport.phoneMute.reportId) {
- logger.warn('handleInputReport:ignore unknown reportId');
-
- return;
- }
-
- let hookStatusChange = false;
- let muteStatusChange = false;
-
- const reportData = new Uint8Array(data.buffer);
- const needReply = true;
-
- if (reportId === inputReport.hookSwitch.reportId) {
- const item = inputReport.hookSwitch;
- const byteIndex = Math.trunc(item.usageOffset / 8);
- const bitPosition = item.usageOffset % 8;
- // eslint-disable-next-line no-bitwise
- const usageOn = (data.getUint8(byteIndex) & (0x01 << bitPosition)) !== 0;
-
- logger.warn('recv hookSwitch ', usageOn ? HOOK_STATUS.OFF : HOOK_STATUS.ON);
- if (inputReport.hookSwitch.isAbsolute) {
- if (this.deviceInfo.hookStatus === HOOK_STATUS.ON && usageOn) {
- this.deviceInfo.hookStatus = HOOK_STATUS.OFF;
- hookStatusChange = true;
- } else if (this.deviceInfo.hookStatus === HOOK_STATUS.OFF && !usageOn) {
- this.deviceInfo.hookStatus = HOOK_STATUS.ON;
- hookStatusChange = true;
- }
- } else if (usageOn) {
- this.deviceInfo.hookStatus = this.deviceInfo.hookStatus === HOOK_STATUS.OFF
- ? HOOK_STATUS.ON : HOOK_STATUS.OFF;
- hookStatusChange = true;
- }
- }
-
- if (reportId === inputReport.phoneMute.reportId) {
- const item = inputReport.phoneMute;
- const byteIndex = Math.trunc(item.usageOffset / 8);
- const bitPosition = item.usageOffset % 8;
- // eslint-disable-next-line no-bitwise
- const usageOn = (data.getUint8(byteIndex) & (0x01 << bitPosition)) !== 0;
-
- logger.warn('recv phoneMute ', usageOn ? HOOK_STATUS.ON : HOOK_STATUS.OFF);
- if (inputReport.phoneMute.isAbsolute) {
- if (this.deviceInfo.muted !== usageOn) {
- this.deviceInfo.muted = usageOn;
- muteStatusChange = true;
- }
- } else if (usageOn) {
- this.deviceInfo.muted = !this.deviceInfo.muted;
- muteStatusChange = true;
- }
- }
-
- const inputReportData = {
- productName: device.productName,
- reportId: this.getHexByte(reportId),
- reportData,
- eventName: '',
- isMute: false,
- hookStatus: ''
- };
-
- if (hookStatusChange) {
- // Answer key state change
- inputReportData.eventName = INPUT_REPORT_EVENT_NAME.ON_DEVICE_HOOK_SWITCH;
- inputReportData.hookStatus = this.deviceInfo.hookStatus;
- logger.warn(`hook status change: ${this.deviceInfo.hookStatus}`);
- }
-
- if (muteStatusChange) {
- // Mute key state change
- inputReportData.eventName = INPUT_REPORT_EVENT_NAME.ON_DEVICE_MUTE_SWITCH;
- inputReportData.isMute = this.deviceInfo.muted;
- logger.warn(`mute status change: ${this.deviceInfo.muted}`);
- }
-
- const actionResult = this.extractActionResult(inputReportData);
-
- this.dispatchEvent(
- new CustomEvent(EVENT_TYPE.UPDATE_DEVICE, {
- detail: {
- actionResult,
- deviceInfo: this.deviceInfo
- }
- })
- );
-
- logger.warn(
- `hookStatusChange=${
- hookStatusChange
- }, muteStatusChange=${
- muteStatusChange
- }, needReply=${
- needReply}`
- );
- if (needReply && (hookStatusChange || muteStatusChange)) {
- let newOffHook;
-
- if (this.deviceInfo.hookStatus === HOOK_STATUS.OFF) {
- newOffHook = true;
- } else if (this.deviceInfo.hookStatus === HOOK_STATUS.ON) {
- newOffHook = false;
- } else {
- logger.warn('Invalid hook status');
-
- return;
- }
- this.sendReplyReport(reportId, newOffHook, this.deviceInfo.muted);
- } else {
- logger.warn(`Not sending reply report: needReply ${needReply},
- hookStatusChange: ${hookStatusChange}, muteStatusChange: ${muteStatusChange}`);
- }
- } catch (e) {
- logger.error(e);
- }
- }
-
- /**
- * Extract action result.
- *
- * @private
- * @param {*} data -.
- * @returns {{eventName: string}} - EventName.
- */
- private extractActionResult(data: any) {
- switch (data.eventName) {
- case INPUT_REPORT_EVENT_NAME.ON_DEVICE_HOOK_SWITCH:
- return {
- eventName: data.hookStatus === HOOK_STATUS.ON
- ? ACTION_HOOK_TYPE_NAME.HOOK_SWITCH_ON : ACTION_HOOK_TYPE_NAME.HOOK_SWITCH_OFF
- };
- case INPUT_REPORT_EVENT_NAME.ON_DEVICE_MUTE_SWITCH:
- return {
- eventName: data.isMute ? ACTION_HOOK_TYPE_NAME.MUTE_SWITCH_ON : ACTION_HOOK_TYPE_NAME.MUTE_SWITCH_OFF
- };
- case 'ondevicevolumechange':
- return {
- eventName: data.volumeStatus === 'up'
- ? ACTION_HOOK_TYPE_NAME.VOLUME_CHANGE_UP : ACTION_HOOK_TYPE_NAME.VOLUME_CHANGE_DOWN
- };
- default:
- break;
- }
- }
-
- /**
- * Reset device state.
- *
- * @returns {void} -.
- */
- resetDeviceState() {
- if (!this.deviceInfo?.device || !this.deviceInfo?.device?.opened) {
- return;
- }
-
- this.deviceInfo.hookStatus = HOOK_STATUS.ON;
- this.deviceInfo.muted = false;
- this.deviceInfo.ring = false;
- this.deviceInfo.hold = false;
-
- this.sendDeviceReport({ command: COMMANDS.ON_HOOK });
- this.sendDeviceReport({ command: COMMANDS.MUTE_OFF });
- }
-
- /**
- * Parse input reports.
- *
- * @param {HIDReportInfo[]} inputReports -.
- * @returns {void} -.
- */
- private parseInputReports(inputReports: HIDReportInfo[]) {
- inputReports.forEach(report => {
- if (!report?.items?.length || report.reportId === undefined) {
- return;
- }
-
- let usageOffset = 0;
-
- report.items.forEach((item: HIDReportItem) => {
- if (
- item.usages === undefined
- || item.reportSize === undefined
- || item.reportCount === undefined
- || item.isAbsolute === undefined
- ) {
- logger.warn('parseInputReports invalid parameters!');
-
- return;
- }
-
- const reportSize = item.reportSize ?? 0;
- const reportId = report.reportId ?? 0;
-
- item.usages.forEach((usage: number, i: number) => {
- switch (usage) {
- case DEVICE_USAGE.hookSwitch.usageId:
- this.deviceCommand.inputReport.hookSwitch = {
- reportId,
- usageOffset: usageOffset + (i * reportSize),
- isAbsolute: item.isAbsolute ?? false
- };
- break;
- case DEVICE_USAGE.phoneMute.usageId:
- this.deviceCommand.inputReport.phoneMute = {
- reportId,
- usageOffset: usageOffset + (i * reportSize),
- isAbsolute: item.isAbsolute ?? false
- };
- break;
- default:
- break;
- }
- });
-
- usageOffset += item.reportCount * item.reportSize;
- });
- });
-
- if (!this.deviceCommand.inputReport.phoneMute || !this.deviceCommand.inputReport.hookSwitch) {
- logger.warn('parseInputReports - no phoneMute or hookSwitch. Skip. Returning false');
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Parse output reports.
- *
- * @private
- * @param {HIDReportInfo[]} outputReports -.
- * @returns {void} -.
- */
- private parseOutputReports(outputReports: HIDReportInfo[]) {
- outputReports.forEach((report: HIDReportInfo) => {
- if (!report?.items?.length || report.reportId === undefined) {
- return;
- }
-
- let usageOffset = 0;
- const usageOffsetMap: Map<number, number> = new Map();
-
- report.items.forEach(item => {
- if (item.usages === undefined || item.reportSize === undefined || item.reportCount === undefined) {
- logger.warn('parseOutputReports invalid parameters!');
-
- return;
- }
-
- const reportSize = item.reportSize ?? 0;
- const reportId = report.reportId ?? 0;
-
- item.usages.forEach((usage: number, i: number) => {
- switch (usage) {
- case DEVICE_USAGE.mute.usageId:
- this.deviceCommand.outputReport.mute = {
- reportId,
- usageOffset: usageOffset + (i * reportSize)
- };
- usageOffsetMap.set(usage, usageOffset + (i * reportSize));
- break;
- case DEVICE_USAGE.offHook.usageId:
- this.deviceCommand.outputReport.offHook = {
- reportId,
- usageOffset: usageOffset + (i * reportSize)
- };
- usageOffsetMap.set(usage, usageOffset + (i * reportSize));
- break;
- case DEVICE_USAGE.ring.usageId:
- this.deviceCommand.outputReport.ring = {
- reportId,
- usageOffset: usageOffset + (i * reportSize)
- };
- usageOffsetMap.set(usage, usageOffset + (i * reportSize));
- break;
- case DEVICE_USAGE.hold.usageId:
- this.deviceCommand.outputReport.hold = {
- reportId,
- usageOffset: usageOffset = i * reportSize
- };
- usageOffsetMap.set(usage, usageOffset + (i * reportSize));
- break;
- default:
- break;
- }
- });
-
- usageOffset += item.reportCount * item.reportSize;
- });
-
- const reportLength = usageOffset;
-
- for (const [ usage, offset ] of usageOffsetMap) {
- this.outputEventGenerators[usage] = (val: number) => {
- const reportData = new Uint8Array(reportLength / 8);
-
- if (offset >= 0 && val) {
- const byteIndex = Math.trunc(offset / 8);
- const bitPosition = offset % 8;
-
- // eslint-disable-next-line no-bitwise
- reportData[byteIndex] = 1 << bitPosition;
- }
-
- return reportData;
- };
- }
- });
-
- let hook, mute, ring;
-
- for (const item in this.outputEventGenerators) {
- if (Object.prototype.hasOwnProperty.call(this.outputEventGenerators, item)) {
- let newItem = this.getHexByte(item);
-
- newItem = `0x0${newItem}`;
- if (DEVICE_USAGE.mute.usageId === Number(newItem)) {
- mute = this.outputEventGenerators[DEVICE_USAGE.mute.usageId];
- } else if (DEVICE_USAGE.offHook.usageId === Number(newItem)) {
- hook = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId];
- } else if (DEVICE_USAGE.ring.usageId === Number(newItem)) {
- ring = this.outputEventGenerators[DEVICE_USAGE.ring.usageId];
- }
- }
- }
- if (!mute && !ring && !hook) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Send device report.
- *
- * @param {{ command: string }} data -.
- * @returns {void} -.
- */
- async sendDeviceReport(data: { command: string; }) {
- if (!data?.command || !this.deviceInfo
- || !this.deviceInfo.device || !this.deviceInfo.device.opened || !this.isParseDescriptorsSuccess) {
- logger.warn('There are currently non-compliant conditions');
-
- return;
- }
-
- logger.warn(`sendDeviceReport data.command: ${data.command}`);
-
- if (data.command === COMMANDS.MUTE_ON || data.command === COMMANDS.MUTE_OFF) {
- if (!this.outputEventGenerators[DEVICE_USAGE.mute.usageId]) {
- logger.warn('current no parse mute event');
-
- return;
- }
- } else if (data.command === COMMANDS.ON_HOOK || data.command === COMMANDS.OFF_HOOK) {
- if (!this.outputEventGenerators[DEVICE_USAGE.offHook.usageId]) {
- logger.warn('current no parse offHook event');
-
- return;
- }
- } else if (data.command === COMMANDS.ON_RING || data.command === COMMANDS.OFF_RING) {
- if (!this.outputEventGenerators[DEVICE_USAGE.ring.usageId]) {
- logger.warn('current no parse ring event');
-
- return;
- }
- }
-
- let oldOffHook;
- let newOffHook;
- let newMuted;
- let newRing;
- let newHold;
- let offHookReport;
- let muteReport;
- let ringReport;
- let holdReport;
- let reportData = new Uint8Array();
-
- const reportId = this.matchReportId(data.command);
-
- if (reportId === 0) {
- logger.warn(`Unsupported command ${data.command}`);
-
- return;
- }
-
- /* keep old status. */
- const oldMuted = this.deviceInfo.muted;
-
- if (this.deviceInfo.hookStatus === HOOK_STATUS.OFF) {
- oldOffHook = true;
- } else if (this.deviceInfo.hookStatus === HOOK_STATUS.ON) {
- oldOffHook = false;
- } else {
- logger.warn('Invalid hook status');
-
- return;
- }
-
- const oldRing = this.deviceInfo.ring;
- const oldHold = this.deviceInfo.hold;
-
- logger.warn(
- `send device command: old_hook=${oldOffHook}, old_muted=${oldMuted}, old_ring=${oldRing}`
- );
-
- /* get new status. */
- switch (data.command) {
- case COMMANDS.MUTE_ON:
- newMuted = true;
- break;
- case COMMANDS.MUTE_OFF:
- newMuted = false;
- break;
- case COMMANDS.ON_HOOK:
- newOffHook = false;
- break;
- case COMMANDS.OFF_HOOK:
- newOffHook = true;
- break;
- case COMMANDS.ON_RING:
- newRing = true;
- break;
- case COMMANDS.OFF_RING:
- newRing = false;
- break;
- case COMMANDS.ON_HOLD:
- newHold = true;
- break;
- case COMMANDS.OFF_HOLD:
- newHold = false;
- break;
- default:
- logger.info(`Unknown command ${data.command}`);
-
- return;
- }
- logger.warn(
- `send device command: new_hook = ${newOffHook}, new_muted = ${newMuted},
- new_ring = ${newRing} new_hold = ${newHold}`
- );
-
- if (this.outputEventGenerators[DEVICE_USAGE.mute.usageId]) {
- if (newMuted === undefined) {
- muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](oldMuted);
- } else {
- muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](newMuted);
- }
- }
-
- if (this.outputEventGenerators[DEVICE_USAGE.offHook.usageId]) {
- if (newOffHook === undefined) {
- offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](oldOffHook);
- } else {
- offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](newOffHook);
- }
- }
-
- if (this.outputEventGenerators[DEVICE_USAGE.ring.usageId]) {
- if (newRing === undefined) {
- ringReport = this.outputEventGenerators[DEVICE_USAGE.ring.usageId](oldRing);
- } else {
- ringReport = this.outputEventGenerators[DEVICE_USAGE.ring.usageId](newRing);
- }
- }
-
- if (this.outputEventGenerators[DEVICE_USAGE.hold.usageId]) {
- holdReport = this.outputEventGenerators[DEVICE_USAGE.hold.usageId](oldHold);
- }
-
- if (reportId === this.deviceCommand.outputReport.mute.reportId) {
- reportData = new Uint8Array(muteReport);
- }
-
- if (reportId === this.deviceCommand.outputReport.offHook.reportId) {
- reportData = new Uint8Array(offHookReport);
- }
-
- if (reportId === this.deviceCommand.outputReport.ring.reportId) {
- reportData = new Uint8Array(ringReport);
- }
-
- if (reportId === this.deviceCommand.outputReport.hold.reportId) {
- reportData = new Uint8Array(holdReport);
- }
-
- logger.warn(`[sendDeviceReport] send device command (before call webhid API)
- ${data.command}: reportId=${reportId}, reportData=${reportData}`);
- logger.warn(`reportData is ${JSON.stringify(reportData, null, ' ')}`);
- await this.deviceInfo.device.sendReport(reportId, reportData);
-
- /* update new status. */
- this.updateDeviceStatus(data);
- }
-
- /**
- * Update device status.
- *
- * @private
- * @param {{ command: string; }} data -.
- * @returns {void}
- */
- private updateDeviceStatus(data: { command: string; }) {
- switch (data.command) {
- case COMMANDS.MUTE_ON:
- this.deviceInfo.muted = true;
- break;
- case COMMANDS.MUTE_OFF:
- this.deviceInfo.muted = false;
- break;
- case COMMANDS.ON_HOOK:
- this.deviceInfo.hookStatus = HOOK_STATUS.ON;
- break;
- case COMMANDS.OFF_HOOK:
- this.deviceInfo.hookStatus = HOOK_STATUS.OFF;
- break;
- case COMMANDS.ON_RING:
- this.deviceInfo.ring = true;
- break;
- case COMMANDS.OFF_RING:
- this.deviceInfo.ring = false;
- break;
- case COMMANDS.ON_HOLD:
- this.deviceInfo.hold = true;
- break;
- case 'offHold':
- this.deviceInfo.hold = false;
- break;
- default:
- logger.warn(`Unknown command ${data.command}`);
- break;
- }
- logger.warn(
- `[updateDeviceStatus] device status after send command: hook=${this.deviceInfo.hookStatus},
- muted=${this.deviceInfo.muted}, ring=${this.deviceInfo.ring}`
- );
- }
-
- /**
- * Math given command with known commands.
- *
- * @private
- * @param {string} command -.
- * @returns {number} ReportId.
- */
- private matchReportId(command: string) {
- switch (command) {
- case COMMANDS.MUTE_ON:
- case COMMANDS.MUTE_OFF:
- return this.deviceCommand.outputReport.mute.reportId;
- case COMMANDS.ON_HOOK:
- case COMMANDS.OFF_HOOK:
- return this.deviceCommand.outputReport.offHook.reportId;
- case COMMANDS.ON_RING:
- case COMMANDS.OFF_RING:
- return this.deviceCommand.outputReport.ring.reportId;
- case COMMANDS.ON_HOLD:
- case COMMANDS.OFF_HOLD:
- return this.deviceCommand.outputReport.hold.reportId;
- default:
- logger.info(`Unknown command ${command}`);
-
- return 0;
- }
- }
-
- /**
- * Send reply report to device.
- *
- * @param {number} inputReportId -.
- * @param {(string | boolean | undefined)} curOffHook -.
- * @param {(string | undefined)} curMuted -.
- * @returns {void} -.
- */
- private async sendReplyReport(
- inputReportId: number,
- curOffHook: string | boolean | undefined,
- curMuted: boolean | string | undefined
- ) {
- const reportId = this.retriveInputReportId(inputReportId);
-
-
- if (!this.deviceInfo?.device || !this.deviceInfo?.device?.opened) {
- logger.warn('[sendReplyReport] device is not opened or does not exist');
-
- return;
- }
-
- if (reportId === 0 || curOffHook === undefined || curMuted === undefined) {
- logger.warn(`[sendReplyReport] return, provided data not valid,
- reportId: ${reportId}, curOffHook: ${curOffHook}, curMuted: ${curMuted}`);
-
- return;
- }
-
- let reportData = new Uint8Array();
- let muteReport;
- let offHookReport;
- let ringReport;
-
- if (this.deviceCommand.outputReport.offHook.reportId === this.deviceCommand.outputReport.mute.reportId) {
- muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](curMuted);
- offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](curOffHook);
- reportData = new Uint8Array(offHookReport);
- for (const [ i, data ] of muteReport.entries()) {
- // eslint-disable-next-line no-bitwise
- reportData[i] |= data;
- }
- } else if (reportId === this.deviceCommand.outputReport.offHook.reportId) {
- offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](curOffHook);
- reportData = new Uint8Array(offHookReport);
- } else if (reportId === this.deviceCommand.outputReport.mute.reportId) {
- muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](curMuted);
- reportData = new Uint8Array(muteReport);
- } else if (reportId === this.deviceCommand.outputReport.ring.reportId) {
- ringReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](curMuted);
- reportData = new Uint8Array(ringReport);
- }
-
- logger.warn(`[sendReplyReport] send device reply: reportId=${reportId}, reportData=${reportData}`);
- await this.deviceInfo.device.sendReport(reportId, reportData);
- }
-
- /**
- * Retrieve input report id.
- *
- * @private
- * @param {number} inputReportId -.
- * @returns {number} ReportId -.
- */
- private retriveInputReportId(inputReportId: number) {
- let reportId = 0;
-
- if (this.deviceCommand.outputReport.offHook.reportId === this.deviceCommand.outputReport.mute.reportId) {
- reportId = this.deviceCommand.outputReport.offHook.reportId;
- } else if (inputReportId === this.deviceCommand.inputReport.hookSwitch.reportId) {
- reportId = this.deviceCommand.outputReport.offHook.reportId;
- } else if (inputReportId === this.deviceCommand.inputReport.phoneMute.reportId) {
- reportId = this.deviceCommand.outputReport.mute.reportId;
- }
-
- return reportId;
- }
-
- /**
- * Get the hexadecimal bytes.
- *
- * @param {number|string} data -.
- * @returns {string}
- */
- getHexByte(data: number | string) {
- let hex = Number(data).toString(16);
-
- while (hex.length < 2) {
- hex = `0${hex}`;
- }
-
- return hex;
- }
- }
|