import { ConfigParamsCloud } from "./common-types"; import { DeviceImpl } from "./device"; import { isNodeJs } from "./help"; import { NativeLogInfo, NativeLogError } from "./logwrapper"; import { YLSdkInterface } from "./sdkinterface"; import util = require('util'); import { FirmwareEventStatus, FirmwareEventType } from "./fw-types"; // Browser friendly type-only import: type EventEmitter = import('events').EventEmitter; let ylSdkInterface: YLSdkInterface; let events: any; if (isNodeJs()) { // These statements should be executed under nodejs only to avoid browserfy problems: let bindings = require('bindings'); ylSdkInterface = bindings('sdkinterface'); events = require('events'); } let ylSdk: Promise | null = null; export function initSdk(appId: string, configParam: ConfigParamsCloud = {}): Promise { if (!isNodeJs) { return Promise.reject(new Error("yl usb sdk need to run under nodejs and not in a browser")); } if (!ylSdk) { ylSdk = new Promise((resolve, reject) => { return new YLSdkImpl(appId, configParam, resolve, reject); }); } else { } return ylSdk; } export namespace YLSdkImplCallbacks { export type attach = (device: DeviceImpl) => void; export type detach = (device: DeviceImpl) => void; export type firstScanDone = () => void; } export type YLSdkImplEvents = 'attach' | 'detach' | 'firstScanDone'; export const ylEventsList: YLSdkImplEvents[] = ['attach', 'detach', 'firstScanDone']; export class YLSdkImpl { private readonly eventEmitter: EventEmitter; private readonly deviceList: Map; private readonly promiseFirceScanDone: Promise; public readonly appId?: string; constructor(appId: string, configParam: ConfigParamsCloud, resolve: (value: YLSdkImpl) => void, reject: (msg: Error) => void) { if (!isNodeJs()) { throw new Error("yl usb sdk need to run under nodejs and not in a browser"); } this.eventEmitter = new events.EventEmitter(); this.deviceList = new Map(); this.appId = appId; this.promiseFirceScanDone = new Promise((firstScanResolve, firstScanReject) => { ylSdkInterface.Initialize(appId, (err, ret) => { try { if (err) { let errMsg = new Error("init failed:" + err); NativeLogError("YLSdkImpl::constructor::initResult", errMsg); reject(errMsg); } else { NativeLogInfo("YLSdkImpl::constructor::initResult", "init success"); resolve(this); } } catch (error) { NativeLogError("YLSdkImpl::constructor::initResult", error); } }, (event_time_ms) => { // first scan done try { NativeLogInfo("YLSdkImpl::constructor::firstScanDone", "yl sdk first scan done"); this.eventEmitter.emit('firstScanDone', undefined); firstScanResolve(); } catch (error) { NativeLogError("YLSdkImpl::constructor::firstScanDone", error); } }, (deviceData, event_time_ms) => { // device attached try { NativeLogInfo("YLSdkImpl::constructor::attached", (() => `device#${deviceData.deviceId}, dev info=${JSON.stringify(deviceData, null, 3)}`)); let deviceImpl = new DeviceImpl(deviceData); this.deviceList.set(deviceData.deviceId, deviceImpl); this.eventEmitter.emit('attach', deviceImpl); } catch (error) { NativeLogError("YLSdkImpl::constructor::attached", error); } }, (deviceId, event_time_ms) => { // device detached try { let deviceImpl = this.deviceList.get(deviceId); if (deviceImpl) { NativeLogInfo("YLSdkImpl::constructor::detached", "device#" + deviceId); // Assign to detached_time_ms even though it is formally a readonly because we don't want clients to change it. //(deviceType.detached_time_ms as DeviceType['detached_time_ms']) = event_time_ms; // When the device enters the upgrade, it will be removed immediately // If the device is removed from the list, the app will not be able to control the device // So, remove it when the upgrade is finish if (deviceImpl.isInUpgradeProgress) { NativeLogInfo("YLSdkImpl::constructor::detached", `device#${deviceId} in update progress, not delete`); } else { this.deviceList.delete(deviceId); this.eventEmitter.emit('detach', deviceImpl); } } else { NativeLogError("YLSdkImpl::constructor::detached", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::detached", err); } }, (deviceId, translatedInData, buttonInData) => { // buttonInDataTranslated try { NativeLogInfo("YLSdkImpl::constructor::buttonInDataTranslated", (() => `device#${deviceId} translatedInData=${translatedInData}, buttonInData=${buttonInData}`)); let device = this.deviceList.get(deviceId); if (device) { device._eventEmitter.emit('btnPress', translatedInData, buttonInData); } else { NativeLogError("YLSdkImpl::constructor::buttonInDataTranslated", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::buttonInDataTranslated callback", err) } }, (deviceId, logEventType, ret) => { // device log export/upload to cloud try { NativeLogInfo("YLSdkImpl::constructor::onDevLogEvent", (() => `device#${deviceId} event:${logEventType} result:${ret}`)); let device = this.deviceList.get(deviceId); if (device) { device._eventEmitter.emit('onDevLogEvent', logEventType, ret); } else { NativeLogError("onDevLogEvent callback", "Could not lookup device with id " + deviceId); } } catch (err) { // Log but do not propagate js errors into native caller (or node process will be aborted): NativeLogError("YLSdkImpl::constructor::onDevLogEvent callback", err) } }, (deviceId, levelInPercent, isCharging, isBatteryLow) => { // battery status changed try { NativeLogInfo("YLSdkImpl::constructor::onBatteryStatusUpdate", (() => `device#${deviceId} levelInPercent=${levelInPercent}, isCharging=${isCharging}, isBatteryLow=${isBatteryLow}`)); let device = this.deviceList.get(deviceId); if (device) { device._eventEmitter.emit('onBatteryStatusUpdate', levelInPercent, isCharging, isBatteryLow); } else { NativeLogError("YLSdkImpl::constructor::onBatteryStatusUpdate", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::onBatteryStatusUpdate", err) } }, (deviceId, type, input) => { // remote mmi try { NativeLogInfo("YLSdkImpl::constructor::onRemoteMmiEvent", (() => `onRemoteMmiEvent event received from native sdk with type=${type}, input=${input}`)); let device = this.deviceList.get(deviceId); if (device) { device._eventEmitter.emit('onRemoteMmiEvent', type, input); } else { NativeLogError("YLSdkImpl::constructor::onRemoteMmiEvent", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::onRemoteMmiEvent", err) } }, (deviceId, type, status, fwProgressPercent) => { // fw event try { NativeLogInfo("YLSdkImpl::constructor::FirmwareProgress", (() => `dev #${deviceId}, type=${type}, status=${status}, percent=${fwProgressPercent}`)); let device = this.deviceList.get(deviceId); if (device) { // after firmware update, remove device from device list if (type == FirmwareEventType.Firmware_Update && (status == FirmwareEventStatus.Completed || status == FirmwareEventStatus.Update_Error)) { NativeLogInfo("YLSdkImpl::constructor::FirmwareProgress", `device#${deviceId} update finish, remove device from list`); this.deviceList.delete(deviceId); } device._eventEmitter.emit('downloadFirmwareProgress', type, status, fwProgressPercent); } else { NativeLogError("YLSdkImpl::constructor::FirmwareProgress", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::FirmwareProgress", err) } }, (deviceId, status, percentage) => { try { NativeLogInfo("YLSdkImpl::constructor::onUploadProgress", (() => `device#${deviceId} status ${status}, percentage ${percentage}`)); let device = this.deviceList.get(deviceId); if (device) { device._eventEmitter.emit('onUploadProgress', status, percentage); } else { NativeLogError("YLSdkImpl::constructor::onUploadProgress", "not in device list" + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::onUploadProgress", err) } }, (deviceId, pairedListInfo) => { // bluetooth pairing list try { NativeLogInfo("YLSdkImpl::constructor::onBTParingListChange", (() => `device#${deviceId} pairedListInfo ${JSON.stringify(pairedListInfo, null, 3)}`)); let device = this.deviceList.get(deviceId); if (device) { device._eventEmitter.emit('onBTParingListChange', pairedListInfo); } else { NativeLogError("YLSdkImpl::constructor::onBTParingListChange", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::onBTParingListChange callback", err) } }, (deviceId, buttonEvents) => { // device btn event try { NativeLogInfo("YLSdkImpl::constructor::OnDeviceBtnEvent", (() => `device#${deviceId} buttonEvents=${buttonEvents}`)); let deviceImpl = this.deviceList.get(deviceId); if (deviceImpl) { deviceImpl._eventEmitter.emit('onHieKeyEvent', buttonEvents); } else { NativeLogError("YLSdkImpl::constructor::OnDeviceBtnEvent", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::OnDeviceBtnEvent", err) } }, (deviceId, cid, translatedInData, buttonInData) => { try { } catch (err) { } }, (deviceId, cid, action) => { try { NativeLogInfo("YLSdkImpl::constructor::OnCallAction", (() => `device#${deviceId} cid=${cid} action=${action}`)); let deviceImpl = this.deviceList.get(deviceId); if (deviceImpl) { deviceImpl._eventEmitter.emit('callAction', cid, action); } else { NativeLogError("YLSdkImpl::constructor::OnCallAction", "not in device list " + deviceId); } } catch (err) { NativeLogError("YLSdkImpl::constructor::OnCallAction", err); } }, (deviceId, cid, dialnumber) => { NativeLogInfo("YLSdkImpl::constructor::onDialNumebrChange", (() => `device#${deviceId} cid=${cid}`)); let deviceImpl = this.deviceList.get(deviceId); if (deviceImpl) { deviceImpl._eventEmitter.emit('onDialNumebrChange', cid, dialnumber); } else { NativeLogError("YLSdkImpl::constructor::onDialNumebrChange", "not in device list " + deviceId); } try { } catch (err) { NativeLogError("YLSdkImpl::constructor::onDialNumebrChange", err); } }, configParam); }); } /** * The user must call this function when finished using the wrapper. Otherwise * the node process will not shutdown properly. * * @param {boolean} shutdownServer Optional parameter only applicable to electronrendererhelper, * where it cause API server shutdown if set. Can safely be ignored otherwise. * @returns {Promise} - Resolve `void` if successful otherwise Reject with `error`. */ disposeAsync(shutdownServer: boolean = false): Promise { this.eventEmitter.removeAllListeners(); let promiseRet: Promise; if (!ylSdkInterface.UnInitialize()) { promiseRet = Promise.reject(new Error("failed uininit")); } else { this.deviceList.clear(); ylSdk = null; promiseRet = Promise.resolve(); } return promiseRet; } firseScanForDeviceDoneAsync(): Promise { NativeLogInfo("interface called", (() => `#${this.firseScanForDeviceDoneAsync.name}`)); return this.promiseFirceScanDone.then(() => { NativeLogInfo("interface returned", (() => `#${this.firseScanForDeviceDoneAsync.name}`)); }) } getAttachedDevice(deviceId: number): DeviceImpl | undefined { let deviceImpl = this.deviceList.get(deviceId); return deviceImpl; } getAttachedDevices(): DeviceImpl[] { const deviceList = Array.from(this.deviceList.values()); return deviceList; } /** * Integrates softphone app to Yealink applications like Yealink USB Connect(YUC). * @param {string} guid Client unique ID. * @param {string} softphoneName Name of the application to be shown in YUC. * @returns {Promise} - Resolve `boolean` if successful otherwise Reject with `error`. * - Returns `true` if softphone app integrates to Yealink application, `false` otherwise. */ connectToYealinkApplicationAsync(guid: string, softphoneName: string): Promise { return util.promisify(ylSdkInterface.ConnectToYealinkApplication)(guid, softphoneName).then((result) => { return result; }); } /** * Disconnects connected from Yealink applications. * @returns {Promise} - Resolve `void` if successful otherwise Reject with `error`. */ disconnectFromYealinkApplicationAsync(): Promise { return util.promisify(ylSdkInterface.DisconnectFromYealinkApplication)().then(() => { }); } /** * Sets the softphone to Ready. Currently applicable for only Yealink USB Connect(YUC). * @param {boolean} isReady Sets the softphone readiness state. * @returns {Promise} - Resolve `void` if successful otherwise Reject with `error`. */ setSoftphoneReadyAsync(isReady: boolean): Promise { return util.promisify(ylSdkInterface.SetSoftphoneReady)(isReady).then(() => { }); } /** * Indicates whether the softphone is in focus. * @returns {Promise} - Resolve `boolean` if successful otherwise Reject with `error`. * - Returns `true` if softphone is in focus, `false` otherwise. */ isSoftphoneInFocusAsync(): Promise { return util.promisify(ylSdkInterface.IsSoftphoneInFocus)().then((result) => { return result; }); } /** * Get the SDK version. * @returns {Promise} - Resolve SDK version `string` if successful otherwise Reject with `error`. */ getSDKVersionAsync(): Promise { return util.promisify(ylSdkInterface.GetVersion)().then((result) => { return result; }); } RegEvent(event: 'firstScanDone', listener: YLSdkImplCallbacks.firstScanDone): this; RegEvent(event: 'attach', listener: YLSdkImplCallbacks.attach): this; RegEvent(event: 'detach', listener: YLSdkImplCallbacks.detach): this; RegEvent(event: YLSdkImplEvents, listener: YLSdkImplCallbacks.firstScanDone | YLSdkImplCallbacks.attach): this { this.eventEmitter.on(event, listener); return this; } UnregEvent(event: 'firstScanDone', listenser: YLSdkImplCallbacks.firstScanDone): this; UnregEvent(event: 'attach', listenser: YLSdkImplCallbacks.attach): this; UnregEvent(event: 'detach', listenser: YLSdkImplCallbacks.detach): this; UnregEvent(event: YLSdkImplEvents, listener: YLSdkImplCallbacks.attach | YLSdkImplCallbacks.detach | YLSdkImplCallbacks.firstScanDone): this { this.eventEmitter.off(event, listener); return this; } }