UNPKG

iobroker.wolf-smartset

Version:
863 lines (770 loc) 35.6 kB
'use strict'; const utils = require('@iobroker/adapter-core'); const wolfsmartset = require('./lib/wss'); // ipify.org REST API: get your public IP const axios = require('axios').default; const _GET_MY_PUBLIC_IP_URL = 'https://api.ipify.org?format=json'; const timeoutHandler = []; let ParamObjList = []; //const objects = {}; class WolfSmartsetAdapter extends utils.Adapter { wss; wss_user; wss_password; myPublicIp; device; onlinePoll; emptyCount; BundleIdShortCycle; ValueIdListShortCycle; BundleIdLongCycle; ValueIdListLongCycle; /** * @param [options] - adapter options */ constructor(options) { super({ ...options, name: 'wolf-smartset', }); this.on('ready', this.onReady.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); // this.on('objectChange', this.onObjectChange.bind(this)); this.on('message', this.onMessage.bind(this)); this.on('unload', this.onUnload.bind(this)); } /** * Get our public IP from ipify.org * */ async _getMyPublicIp() { if (this.config.doPubIpCheck) { try { const myIpDataResponse = await axios.get(_GET_MY_PUBLIC_IP_URL); if (myIpDataResponse.status == 200 && myIpDataResponse.data && myIpDataResponse.data.ip) { return myIpDataResponse.data.ip; } } catch (error) { this.log.warn(`_getMyPublicIp() failed: ${error.message}`); } } return null; } async _getParamsWebGui(guiData) { if (guiData == null) { return; } const param = []; guiData.MenuItems.forEach(MenuItem => { const tabName = MenuItem.Name; // Benutzerebene: iterate over MenuItems MenuItem.TabViews.forEach(TabView => { const tabName2 = `${tabName}.${TabView.TabName}`; // BundleId and GuiId of TabView are required in each param below thie TabView const TabViewBundleId = TabView.BundleId; const TabViewGuiId = TabView.GuiId; TabView.ParameterDescriptors.forEach(ParameterDescriptor => { var tabName3; if (typeof ParameterDescriptor.Group !== 'undefined') { tabName3 = `${tabName2}.${ParameterDescriptor.Group.replace(' ', '_')}`; } else { tabName3 = tabName2; } // ignore pseudo or intermediate/complex parameters (e.g list of time programs) if (ParameterDescriptor.ParameterId > 0) { const paramInfo = ParameterDescriptor; paramInfo.BundleId = TabViewBundleId; paramInfo.GuiId = TabViewGuiId; //search duplicate const find = param.find(element => element.ParameterId === paramInfo.ParameterId); if (!find) { paramInfo.TabName = tabName3; // remove subtree if exists // delete paramInfo.ChildParameterDescriptors; param.push(paramInfo); } } // Check for ChildParameterDescriptors (e.g. time program) if (typeof ParameterDescriptor.ChildParameterDescriptors !== 'undefined') { ParameterDescriptor.ChildParameterDescriptors.forEach(ChildParameterDescriptor => { var tabName4 = `${tabName3}.${ParameterDescriptor.Name}`; // ignore pseudo or intermediate/complex parameters (e.g time program) if ( ChildParameterDescriptor.NoDataPoint == false && ChildParameterDescriptor.ParameterId > 0 ) { const paramInfo = ChildParameterDescriptor; paramInfo.BundleId = TabViewBundleId; paramInfo.GuiId = TabViewGuiId; //search duplicate const find = param.find(element => element.ParameterId === paramInfo.ParameterId); if (!find) { paramInfo.TabName = tabName4.replace(' ', '_'); param.push(paramInfo); } } if (typeof ChildParameterDescriptor.ChildParameterDescriptors !== 'undefined') { ChildParameterDescriptor.ChildParameterDescriptors.forEach( ChildChildParameterDescriptor => { const tabName5 = `${tabName4}.${ChildParameterDescriptor.Name}`; if (ChildChildParameterDescriptor.ParameterId > 0) { const paramInfo = ChildChildParameterDescriptor; paramInfo.BundleId = TabViewBundleId; paramInfo.GuiId = TabViewGuiId; //search duplicate const find = param.find( element => element.ParameterId === paramInfo.ParameterId, ); if (!find) { paramInfo.TabName = tabName5.replace(' ', '_'); param.push(paramInfo); } } }, ); } }); } }); }); // Fachmannebene: interate over SubMenuEntries MenuItem.SubMenuEntries.forEach(SubMenuEntry => { const tabName2 = `${tabName}.${SubMenuEntry.Name}`; SubMenuEntry.TabViews.forEach(TabView => { const tabName3 = `${tabName2}.${TabView.TabName}`.replace(' ', '_'); // BundleId and GuiId of TabView are required in each param below thie TabView const TabViewBundleId = TabView.BundleId; const TabViewGuiId = TabView.GuiId; TabView.ParameterDescriptors.forEach(ParameterDescriptor => { var tabName4; if (typeof ParameterDescriptor.Group !== 'undefined') { tabName4 = `${tabName3}.${ParameterDescriptor.Group.replace(' ', '_')}`; } else { tabName4 = tabName3; } // ignore pseudo or intermediate/complex parameters (e.g list of time programs) if (ParameterDescriptor.ParameterId > 0) { const paramInfo = ParameterDescriptor; paramInfo.BundleId = TabViewBundleId; paramInfo.GuiId = TabViewGuiId; //search duplicate const find = param.find(element => element.ParameterId === paramInfo.ParameterId); if (!find) { paramInfo.TabName = tabName4; param.push(paramInfo); } } }); }); }); }); return param; } /** * Generates folders, channels and adapter object states for each param in WolfParamDescriptions. * * @param WolfParamDescriptions - flat list of ParamDescriptions for each state returned by getParamsWebGui() */ async _CreateObjects(WolfParamDescriptions) { // get list of instance objects before fetching new list of params from Wolf server const oldInstanceObjects = await this.getForeignObjectsAsync(`${this.namespace}.*`); const collectedChannels = {}; // 1.: Create states for (const WolfParamDescription of WolfParamDescriptions) { // export BundleId of object to associated channel collectedChannels[`${WolfParamDescription.TabName}`] = WolfParamDescription.BundleId; const id = `${WolfParamDescription.TabName}.${WolfParamDescription.ParameterId.toString()}`; const common = { name: typeof WolfParamDescription.NamePrefix !== 'undefined' ? `${WolfParamDescription.NamePrefix}: ${WolfParamDescription.Name}` : WolfParamDescription.Name, // do not declare type and role here to avoid typecheck errors in setObjectNotExists() // type: 'number', // role: 'value', read: true, write: !WolfParamDescription.IsReadOnly, }; common.type = 'number'; common.role = 'value'; // Wolf ControlTypes: // 0: Unknown // 1: Enum w/ ListItems (simple) // 5: Bool // 6: Number; 'Decimals' = decimal places (accuracy) // 9: Date // 10: Time // 13: list of time programs (1, 2 or 3) (not a Value) // 14: list of time ranges // 19: time program (Mon - Sun) (not a value) // 20: Name, SerialNo, MacAddr, SW-Version, HW-Version // 21: IPv4 addr or netmask // 31: Number of any kind // 35: Enum w/ ListItems (w/ Image, Decription, ...) if (WolfParamDescription.ControlType === 5) { //Boolean text common.type = 'boolean'; common.role = WolfParamDescription.IsReadOnly ? 'indicator' : 'switch'; } else if ( WolfParamDescription.ControlType === 9 || WolfParamDescription.ControlType === 10 || WolfParamDescription.ControlType === 14 || WolfParamDescription.ControlType === 20 || WolfParamDescription.ControlType === 21 ) { common.type = 'string'; common.role = 'text'; } else { if (typeof WolfParamDescription.Unit !== 'undefined') { common.unit = WolfParamDescription.Unit; } // thresholds min/max : use Min/MaxValueCondition if available, otherwise use MinValue/MaxValue // Min/MaxValue might be wrong in case of floats, whereas Min/MaxValueCondition seem to be always correct if (typeof WolfParamDescription.MinValue !== 'undefined') { common.min = WolfParamDescription.MinValue; } if (typeof WolfParamDescription.MinValueCondition !== 'undefined') { common.min = parseFloat(WolfParamDescription.MinValueCondition); } if (typeof WolfParamDescription.MaxValue !== 'undefined') { common.max = WolfParamDescription.MaxValue; } if (typeof WolfParamDescription.MaxValueCondition !== 'undefined') { common.max = parseFloat(WolfParamDescription.MaxValueCondition); } if (typeof WolfParamDescription.StepWidth !== 'undefined') { common.step = WolfParamDescription.StepWidth; } if (typeof WolfParamDescription.ListItems !== 'undefined') { const states = {}; WolfParamDescription.ListItems.forEach(ListItems => { states[ListItems.Value] = ListItems.DisplayText; }); common.states = states; } } this.log.debug( `WolfParamDescription ${JSON.stringify(WolfParamDescription)} --> ioBrokerObj.common ${JSON.stringify(common)}`, ); // if this is a new object, create it first const fullId = `${this.namespace}.${id}`; if (typeof oldInstanceObjects[`${fullId}`] == 'undefined') { // create object w/ minimum set of attributes this.setObjectNotExists(id, { type: 'state', common: { name: common.name, type: common.type, role: common.role, read: common.read, write: common.write, }, native: {}, }); } else { oldInstanceObjects[fullId].common.desc = 'active'; } // set all attributes for object this.extendObject(id, { type: 'state', common: common, native: { ValueId: WolfParamDescription.ValueId, ParameterId: WolfParamDescription.ParameterId, ControlType: WolfParamDescription.ControlType, }, }); // 2.: Update object states await this._setStatesWithDiffTypes(WolfParamDescription.ControlType, id, WolfParamDescription.Value); } // 3.: mark obsoleted objects for (const fullId in oldInstanceObjects) { let re = new RegExp(String.raw`^${this.namespace}.info`, 'g'); if ( !fullId.match(re) && typeof oldInstanceObjects[fullId].common != 'undefined' && typeof oldInstanceObjects[fullId].common.desc == 'undefined' ) { oldInstanceObjects[fullId].common.desc = 'obsoleted'; this.extendObject(fullId, oldInstanceObjects[fullId]); } } // 4.: Create folders and channels const createdObjects = {}; for (const [channel, bundleId] of Object.entries(collectedChannels)) { const name = `${channel.split('.').pop()} (Bundle: ${bundleId})`; this.extendObject(channel, { type: 'channel', common: { name: name, }, native: {}, }); createdObjects[channel] = true; this.log.debug(`Create channel ${channel}`); const channelParts = channel.split('.'); let id = channelParts.shift() || ''; let folderName = id; while (id && channelParts.length > 0) { if (!createdObjects[id]) { await this.extendObject(id, { type: 'folder', common: { name: folderName, }, native: {}, }); this.log.debug(`Create folder ${id}`); createdObjects[id] = true; } folderName = channelParts.shift() || ''; if (!folderName) { break; } id += `.${folderName}`; } } this.log.debug('createParams DONE'); } /** * Creates a list of ParameterId for each BundleId defined in WolfParamDescriptions and * From that create ValueIdListShortCycle and ValueIdListLongCycle * * @param WolfParamDescriptions - list of extended WolfParamDescriptions returned by getParamsWebGui() */ async _CreateBundleValuesLists(WolfParamDescriptions) { let BundleValuesList = {}; let DefaultBundleIdShortCycle = 0; let ValueIdListShortCycle = []; let DefaultBundleIdLongCycle = 0; let ValueIdListLongCycle = []; for (const WolfParamDescription of WolfParamDescriptions) { const bundleId = WolfParamDescription.BundleId; if (typeof BundleValuesList[bundleId] == 'undefined') { BundleValuesList[bundleId] = []; } // De-duplicate ParamterIds for bundleId: they might be at multiple locations in the tree if (typeof BundleValuesList[bundleId][WolfParamDescription.ParameterId] == 'undefined') { BundleValuesList[bundleId].push(WolfParamDescription.ParameterId); } } for (const bundleId of this.config.bundleIdTable) { if (bundleId.bundleIdUseShort && typeof BundleValuesList[bundleId.bundleIdName] != 'undefined') { ValueIdListShortCycle = ValueIdListShortCycle.concat(BundleValuesList[bundleId.bundleIdName]); DefaultBundleIdShortCycle = Number(bundleId.bundleIdName) > DefaultBundleIdShortCycle ? Number(bundleId.bundleIdName) : DefaultBundleIdShortCycle; } if (bundleId.bundleIdUseLong && typeof BundleValuesList[bundleId.bundleIdName] != 'undefined') { ValueIdListLongCycle = ValueIdListLongCycle.concat(BundleValuesList[bundleId.bundleIdName]); DefaultBundleIdLongCycle = Number(bundleId.bundleIdName) > DefaultBundleIdLongCycle ? Number(bundleId.bundleIdName) : DefaultBundleIdLongCycle; } } this.BundleIdShortCycle = this.config.bundleIdRequestedShort == 'Default' ? DefaultBundleIdShortCycle : this.config.bundleIdRequestedShort; this.ValueIdListShortCycle = ValueIdListShortCycle; this.BundleIdLongCycle = this.config.bundleIdRequestedLong == 'Default' ? DefaultBundleIdLongCycle : this.config.bundleIdRequestedLong; this.ValueIdListLongCycle = ValueIdListLongCycle; } async _setStatesWithDiffTypes(type, id, value) { if (type == null || id == null || value == null) { return; } // Wolf ControlTypes: // 0: Unknown // 1: Enum w/ ListItems (simple) // 5: Bool // 6: Number; 'Decimals' = decimal places (accuracy) // 9: Date // 10: Time // 13: list of time programs (1, 2 or 3) (not a Value) // 14: list of time ranges // 19: time program (Mon - Sun) (not a value) // 20: Name, SerialNo, MacAddr, SW-Version, HW-Version // 21: IPv4 addr or netmask // 31: Number of any kind // 35: Enum w/ ListItems (w/ Image, Decription, ...) switch (type) { case 5: this.setState(id, { val: value === 'True' ? true : false, ack: true, }); break; case 9: case 10: case 14: case 20: case 21: this.setState(id, { val: value.toString(), ack: true, }); break; default: this.setState(id, { val: parseFloat(value), ack: true, }); break; } } /** * Poll parameter values from Wolf server as configured for the given poll cycle * * @param pollCycle - 'short or 'long' */ async _PollValueList(pollCycle) { try { const recValList = await this.wss.getValList( this.device.GatewayId, this.device.SystemId, pollCycle == 'short' ? this.BundleIdShortCycle : this.BundleIdLongCycle, pollCycle == 'short' ? this.ValueIdListShortCycle : this.ValueIdListLongCycle, pollCycle, ); if (recValList) { await this._SetStatesArray(recValList); } } catch (error) { this.log.warn(error); } } /** * Handler for Short Poll Cycle: poll parameter values from Wolf server and * additionally, every 4th poll cycle: getSystemState and check for PublicIP changes * */ async _ShortPollValueList() { timeoutHandler['shortPollTimeout'] && clearTimeout(timeoutHandler['shortPollTimeout']); await this._PollValueList('short'); this.onlinePoll++; if (this.onlinePoll > 4) { this.onlinePoll = 0; try { const myPublicIp = await this._getMyPublicIp(); if (myPublicIp && this.myPublicIp && myPublicIp != this.myPublicIp) { this.log.warn( `_ShortPollValueList(); PubIP changed from ${this.myPublicIp} to ${myPublicIp}: triggering reload...`, ); await this._mainloop(myPublicIp); return; } const systemStatus = await this.wss.getSystemState(parseInt(this.device.SystemId)); if (systemStatus && typeof systemStatus.IsOnline !== 'undefined') { this.setState('info.connection', { val: systemStatus.IsOnline, ack: true, }); } else { this.setState('info.connection', { val: false, ack: true, }); } } catch (error) { this.log.warn(error); } } timeoutHandler['shortPollTimeout'] = setTimeout(() => { this._ShortPollValueList(); }, this.config.pollIntervalShort * 1000); } /** * Handler for Lhort Poll Cycle: poll parameter values from Wolf server * */ async _LongPollValueList() { timeoutHandler['longPollTimeout'] && clearTimeout(timeoutHandler['longPollTimeout']); await this._PollValueList('long'); timeoutHandler['longPollTimeout'] = setTimeout( () => { this._LongPollValueList(); }, // pollIntervalLong is given in minutes; add 5 seconds to avoid parallel execution w/ _ShortPollValueList() this.config.pollIntervalLong * 60 * 1000 + 5000, ); } async _SetStatesArray(array) { if (array.Values.length === 0) { this.emptyCount++; } else { this.emptyCount = 0; } if (this.emptyCount >= 10) { // no data for long time try a restart this.emptyCount = 0; await this._mainloop(null); return; } array.Values.forEach(recVal => { //this.log.debug("search:" + JSON.stringify(recVal)); //find ParamId for ValueId const findParamObj = ParamObjList.find(element => element.ValueId === recVal.ValueId); if (findParamObj) { for (const key in this.objects) { if (this.objects[key].native && this.objects[key].native.ParameterId === findParamObj.ParameterId) { this._setStatesWithDiffTypes(this.objects[key].native.ControlType, key, recVal.Value); } } } }); } /** * main loop is called from onReady(), and in case of an error by _ShortPollValueList(), _SetStatesArray() and itself * * @param myPublicIp - my current public IP or null if unknown */ async _mainloop(myPublicIp) { timeoutHandler['restartTimeout'] && clearTimeout(timeoutHandler['restartTimeout']); timeoutHandler['shortPollTimeout'] && clearTimeout(timeoutHandler['shortPollTimeout']); timeoutHandler['longPollTimeout'] && clearTimeout(timeoutHandler['longPollTimeout']); try { // Note: Wolf Smartset is IP address aware: if we changed or IP, we have to re-init // if we have a wss matching our configured u/p and our current public IP then use it ... if (!myPublicIp) { myPublicIp = await this._getMyPublicIp(); } if ( !this.wss || this.wss_user != this.config.username || this.wss_password != this.config.password || (myPublicIp && this.myPublicIp && this.myPublicIp != myPublicIp) ) { // ... otherwise kill old wss object and create a new one this.wss && (await this.wss.stop()); this.wss = new wolfsmartset(this.config.username, this.config.password, this); this.wss_user = this.config.username; this.wss_password = this.config.password; if (myPublicIp) { this.myPublicIp = myPublicIp; } } const wssInitialized = await this.wss.init(); if (!wssInitialized) { throw new Error('Could not initialized WSS session'); } const GUIDesc = await this.wss.getGUIDescription(this.device.GatewayId, this.device.SystemId); if (GUIDesc) { ParamObjList = (await this._getParamsWebGui(GUIDesc)) || []; } else { throw new Error('Could not get GUIDescription (device might be down)'); } if (ParamObjList) { await this._CreateObjects(ParamObjList); // create a list of params for each BundleId as defined in the GUI Desc await this._CreateBundleValuesLists(ParamObjList); } this.objects = await this.getForeignObjectsAsync(`${this.namespace}.*`); this.log.debug(JSON.stringify(this.objects)); await this._LongPollValueList(); await this._ShortPollValueList(); } catch (error) { this.log.warn(error); this.log.warn('Trying again in 60 sec...'); timeoutHandler['restartTimeout'] = setTimeout(async () => { this._mainloop(null); }, 60000); } this.subscribeStates('*'); } // /** // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, adminUI... // * Using this method requires "common.messagebox" property to be set to true in io-package.json // * The main purpose for this handler is to handle Device Listing and Device Confirm from the adapter instance settings UI. // // * @param {ioBroker.Message} obj // */ async onMessage(obj) { if (typeof obj === 'object' && obj.message) { if (obj.command === 'send') { // e.g. send email or pushover or whatever this.log.info('send command'); // Send response in callback if required if (obj.callback) { this.sendTo(obj.from, obj.command, 'Message received', obj.callback); } } // getDeviceList: triggered by adapter instance settings UI object 'deviceSelect' if (obj.command === 'getDeviceList') { this.log.debug('getDeviceList ...'); let myPublicIp; let devicelist; let getDeviceListResponse; let adminWss; try { if (obj.message.username == '' || obj.message.password == '') { throw new Error('Please set username and password'); } // check if we can use an already existing wss object from running adapter instance, otherwise create one // Note: Wolf Smartset is IP address aware: if we changed or IP, we have to re-init myPublicIp = await this._getMyPublicIp(); if ( !this.wss || this.wss_user != obj.message.username || this.wss_password != obj.message.password || (myPublicIp && this.myPublicIp && this.myPublicIp != myPublicIp) ) { adminWss = new wolfsmartset(obj.message.username, obj.message.password, this); } else { adminWss = this.wss; } devicelist = await adminWss.adminGetDevicelist(); if (typeof devicelist !== 'undefined') { function convertToSelectEntry(value) { return { label: value.Name, value: JSON.stringify(value) }; } getDeviceListResponse = devicelist.map(convertToSelectEntry); this.log.debug(`getDeviceList: returning '${JSON.stringify(getDeviceListResponse)}`); } else { getDeviceListResponse = [{ label: 'No devices found', value: '' }]; this.log.debug(`getDeviceList: got no devicelist`); } } catch (error) { getDeviceListResponse = [{ label: error.message, value: '' }]; this.wss = null; this.log.debug(`getDeviceList: got error '${error.message}'`); } this.sendTo(obj.from, obj.command, getDeviceListResponse, obj.callback); // if getDeviceList was successful and this adapter instance has currently no wss object // then store our wss instance for use by adapter instance if (typeof devicelist !== 'undefined' && devicelist.length > 0 && !this.wss) { this.wss = adminWss; this.wss_user = obj.message.username; this.wss_password = obj.message.password; if (myPublicIp) { this.myPublicIp = myPublicIp; } } } // confirmDevice: triggered by adapter instance settings UI object 'deviceConfirm' if (obj.command === 'confirmDevice') { this.log.info('confirmDevice'); let myDevice; let confirmDeviceResponse; try { let jsonStringNoCrNl = obj.message.deviceObject.replace(/[\r\n]/g, ' '); myDevice = JSON.parse(jsonStringNoCrNl); if ( typeof myDevice.Name !== 'undefined' && typeof myDevice.Id !== 'undefined' && typeof myDevice.GatewayId !== 'undefined' ) { confirmDeviceResponse = { native: { deviceName: `${myDevice.Name}`, device: jsonStringNoCrNl, }, }; } else { confirmDeviceResponse = { error: `No valid device selected: got '${obj.message.deviceObject}'`, }; } } catch (error) { confirmDeviceResponse = { error: `No device selected: got '${obj.message.deviceObject}', error: ${error.message}`, }; } this.sendTo(obj.from, obj.command, confirmDeviceResponse, obj.callback); } } } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { this.onlinePoll = 4; this.emptyCount = 0; try { this.device = JSON.parse(this.config.device); if (typeof this.device.Id !== 'undefined') { this.device.SystemId = this.device.Id; } if ( this.config.username !== '' && this.config.password !== '' && this.config.deviceName !== '' && typeof this.device.GatewayId !== 'undefined' && typeof this.device.SystemId !== 'undefined' ) { const myPublicIp = await this._getMyPublicIp(); await this._mainloop(myPublicIp); } else { this.log.warn('Please configure username, password and device in adapter instance settings'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.log.warn('Please configure username, password and device in adapter instance settings'); } } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * * @param callback - callback function */ onUnload(callback) { try { timeoutHandler['shortPollTimeout'] && clearTimeout(timeoutHandler['shortPollTimeout']); timeoutHandler['longPollTimeout'] && clearTimeout(timeoutHandler['longPollTimeout']); timeoutHandler['restartTimeout'] && clearTimeout(timeoutHandler['restartTimeout']); this.wss.stop(); this.wss = null; callback(); } catch { callback(); } } /** * Is called if a subscribed state changes * * @param id - value id * @param state - value state */ async onStateChange(id, state) { if (state && !state.ack) { //const ParamId = id.split('.').pop(); const obj = await this.getObjectAsync(id); if (obj) { const findParamObj = ParamObjList.find(element => element.ParameterId === obj.native.ParameterId); this.log.info(`Change value for: ${obj.common.name}: ${JSON.stringify(state)}`); try { const answer = await this.wss.setValList(this.device.GatewayId, this.device.SystemId, [ { ValueId: findParamObj.ValueId, ParameterId: obj.native.ParameterId, Value: String(state.val), ParameterName: obj.common.name, }, ]); if (typeof answer.Values !== 'undefined') { this.setState(id, { val: state.val, ack: true, }); await this._SetStatesArray(answer); } } catch (err) { this.log.error(err); } } } } } // @ts-expect-error parent is a valid property on module if (module.parent) { // Export the constructor in compact mode module.exports = options => new WolfSmartsetAdapter(options); } else { // otherwise start the instance directly new WolfSmartsetAdapter(); }