UNPKG

node-red-contrib-iiot-opcua

Version:

An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.

1,054 lines 53.8 kB
/*
 The BSD 3-Clause License

 Copyright 2022 - DATATRONiQ GmbH (https://datatroniq.com)
 Copyright (c) 2018-2022 Klaus Landsdorf (http://node-red.plus/)
 Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc. (node-red-contrib-opcua)
 All rights reserved.
 node-red-contrib-iiot-opcua
 */
'use strict';
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const opcua_iiot_core_1 = require("./core/opcua-iiot-core");
const node_opcua_1 = require("node-opcua");
const opcua_iiot_core_connector_1 = __importStar(require("./core/opcua-iiot-core-connector"));
const underscore_1 = __importStar(require("underscore"));
const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints");
var internalDebugLog = opcua_iiot_core_connector_1.logger.internalDebugLog;
var detailDebugLog = opcua_iiot_core_connector_1.logger.detailDebugLog;
const helpers_1 = require("./types/helpers");
/**
 * OPC UA connector Node-RED config this.
 *
 * @param RED
 */
module.exports = function (RED) {
    // SOURCE-MAP-REQUIRED
    function OPCUAIIoTConnectorConfiguration(config) {
        var _a;
        const CONNECTION_START_DELAY = 2000; // msec.
        const CONNECTION_STOP_DELAY = 2000; // msec.
        const RECONNECT_DELAY = 1000; // msec.
        const UNLIMITED_LISTENERS = 0;
        RED.nodes.createNode(this, config);
        // HTML settings
        this.discoveryUrl = config.discoveryUrl || null;
        this.endpoint = config.endpoint;
        this.endpointMustExist = config.endpointMustExist || false;
        this.keepSessionAlive = config.keepSessionAlive;
        this.loginEnabled = config.loginEnabled;
        this.name = config.name;
        this.showErrors = config.showErrors;
        this.securityPolicy = (0, node_opcua_1.coerceSecurityPolicy)(config.securityPolicy);
        this.messageSecurityMode = (0, node_opcua_1.coerceMessageSecurityMode)(config.securityMode) || node_opcua_1.MessageSecurityMode.None;
        this.individualCerts = config.individualCerts;
        this.publicCertificateFile = config.publicCertificateFile;
        this.privateKeyFile = config.privateKeyFile;
        this.defaultSecureTokenLifetime = config.defaultSecureTokenLifetime || 120000;
        this.autoSelectRightEndpoint = config.autoSelectRightEndpoint;
        this.strategyMaxRetry = config.strategyMaxRetry || 10000;
        this.strategyInitialDelay = config.strategyInitialDelay || 1000;
        this.strategyMaxDelay = parseInt(config.strategyMaxDelay) || 30000;
        this.strategyRandomisationFactor = config.strategyRandomisationFactor || 0.2;
        this.requestedSessionTimeout = config.requestedSessionTimeout || 60000;
        this.connectionStartDelay = config.connectionStartDelay || CONNECTION_START_DELAY;
        this.reconnectDelay = config.reconnectDelay || RECONNECT_DELAY;
        this.connectionStopDelay = config.connectionStopDelay || CONNECTION_STOP_DELAY;
        this.maxBadSessionRequests = parseInt((_a = config.maxBadSessionRequests) === null || _a === void 0 ? void 0 : _a.toString()) || 10;
        this.iiot = opcua_iiot_core_connector_1.default.initConnectorNode();
        if (!this.iiot)
            throw Error('IIoT Initialization Failed');
        this.setMaxListeners(UNLIMITED_LISTENERS);
        const self = this;
        internalDebugLog('Open Connector Node');
        let sessionStartTimeout;
        let clientStartTimeout;
        let disconnectTimeout;
        let nodeOPCUAClientPath = (0, opcua_iiot_core_1.getNodeOPCUAClientPath)();
        this.securedCommunication = (this.securityPolicy !== node_opcua_1.SecurityPolicy.None &&
            this.messageSecurityMode !== node_opcua_1.MessageSecurityMode.None);
        detailDebugLog('config: ' + this.publicCertificateFile);
        detailDebugLog('config: ' + this.privateKeyFile);
        detailDebugLog('securedCommunication: ' + this.securedCommunication.toString());
        const initCertificatesAndKeys = () => {
            if (this.securedCommunication) {
                this.publicCertificateFile = this.publicCertificateFile || path.join(nodeOPCUAClientPath, '/certificates/client_selfsigned_cert_1024.pem');
                detailDebugLog('using cert: ' + this.publicCertificateFile);
                this.privateKeyFile = this.privateKeyFile || path.join(nodeOPCUAClientPath, '/certificates/PKI/own/private/private_key.pem');
                detailDebugLog('using key: ' + this.privateKeyFile);
            }
            else {
                this.publicCertificateFile = null;
                this.privateKeyFile = null;
            }
        };
        if (this.loginEnabled) {
            if (this.credentials) {
                this.iiot.userIdentity = {
                    type: node_opcua_service_endpoints_1.UserTokenType.UserName,
                    userName: this.credentials.user,
                    password: this.credentials.password
                };
                internalDebugLog('Connecting With Login Data On ' + this.endpoint);
            }
            else {
                /* istanbul ignore next */
                this.error(new Error('Login Enabled But No Credentials'), { payload: '' });
            }
        }
        /*  #########   CONNECTION  #########     */
        const getUpdatedServerOptions = () => {
            var _a, _b;
            initCertificatesAndKeys();
            return {
                securityPolicy: this.securityPolicy,
                securityMode: this.messageSecurityMode,
                defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
                keepSessionAlive: this.keepSessionAlive,
                certificateFile: (_a = this.publicCertificateFile) !== null && _a !== void 0 ? _a : undefined,
                privateKeyFile: (_b = this.privateKeyFile) !== null && _b !== void 0 ? _b : undefined,
                endpointMustExist: this.endpointMustExist,
                requestedSessionTimeout: this.requestedSessionTimeout,
                connectionStrategy: {
                    maxRetry: this.strategyMaxRetry,
                    initialDelay: this.strategyInitialDelay,
                    maxDelay: this.strategyMaxDelay,
                    randomisationFactor: this.strategyRandomisationFactor
                }
            };
        };
        const statusHandler = (status) => {
            this.status(status);
        };
        const errorHandler = (err) => {
            this.error(err);
        };
        const connectOPCUAEndpoint = () => __awaiter(this, void 0, void 0, function* () {
            if ((0, underscore_1.isUndefined)(this.iiot))
                return;
            if (!opcua_iiot_core_connector_1.default.checkEndpoint(this.endpoint, errorHandler)) {
                return;
            }
            internalDebugLog('Connecting To Endpoint ' + this.endpoint);
            this.iiot.opcuaClientOptions = getUpdatedServerOptions();
            if (!this.iiot.opcuaClient)
                this.iiot.opcuaClient = node_opcua_1.OPCUAClient.create(Object.assign({}, this.iiot.opcuaClientOptions)); // Need to use the spread operator, because otherwise there is phantom circular references
            if (Object.keys(this.iiot.opcuaClient).length === 0) {
                /* istanbul ignore next */
                detailDebugLog('Failed to create OPCUA Client ', { opcuaClient: this.iiot.opcuaClient });
            }
            if (this.autoSelectRightEndpoint) {
                autoSelectEndpointFromConnection();
            }
            // coreConnector.setListenerToClient(node)
            connectToClient();
        });
        const connectToClient = () => {
            if ((0, underscore_1.isUndefined)(this.iiot))
                return;
            if ((0, underscore_1.isUndefined)(this.iiot.stateService))
                return;
            // Needs to be separate if so that typescript understands the types properly
            if ((0, underscore_1.isUndefined)(this.iiot.opcuaClient))
                return;
            if (!opcua_iiot_core_connector_1.default.checkEndpoint(this.endpoint, errorHandler)) {
                return;
            }
            this.iiot.opcuaClient.connect(this.endpoint, (err) => {
                var _a;
                if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this) && !(0, underscore_1.isUndefined)(this.iiot) && !(0, underscore_1.isUndefined)(this.iiot.stateService)) {
                    if (err) {
                        //this.iiot.stateMachine.lock().stopopcua()
                        (_a = this.iiot) === null || _a === void 0 ? void 0 : _a.stateService.send('STOP');
                        handleError(err);
                    }
                    else {
                        internalDebugLog('Client Is Connected To ' + this.endpoint);
                        //this.iiot.stateMachine.open()
                        this.iiot.stateService.send('OPEN');
                    }
                }
                else {
                    /* istanbul ignore next */
                    internalDebugLog('iiot not valid on connect resolve');
                }
            });
        };
        const renewConnection = (done) => {
            if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                opcuaDirectDisconnect(() => {
                    if ((0, underscore_1.isUndefined)(this.iiot))
                        return;
                    renewFiniteStateMachine();
                    //this.iiot.stateMachine.idle().initopcua();
                    // Todo: the steps have to be used as before
                    this.iiot.stateService.send('IDLE');
                    this.iiot.stateService.send('INITOPCUA');
                    done();
                });
            }
            else {
                /* istanbul ignore next */
                internalDebugLog('iiot not valid on renew connection');
            }
        };
        const endpointMatchForConnecting = (endpoint) => {
            var _a, _b, _c;
            internalDebugLog('Auto Endpoint ' + ((_a = endpoint.endpointUrl) === null || _a === void 0 ? void 0 : _a.toString()) + ' ' + ((_b = endpoint.securityPolicyUri) === null || _b === void 0 ? void 0 : _b.toString()));
            let securityMode = endpoint.securityMode;
            let securityPolicy = ((_c = endpoint.securityPolicyUri) === null || _c === void 0 ? void 0 : _c.includes('SecurityPolicy#')) ? endpoint.securityPolicyUri.split('#')[1] : endpoint.securityPolicyUri;
            internalDebugLog('node-mode:' + this.messageSecurityMode + ' securityMode: ' + securityMode);
            internalDebugLog('node-policy:' + this.securityPolicy + ' securityPolicy: ' + securityPolicy);
            return (securityMode === this.messageSecurityMode && securityPolicy === this.securityPolicy);
        };
        const selectEndpointFromSettings = (discoverClient) => {
            discoverClient.getEndpoints((err, endpoints) => {
                if (err) {
                    /* istanbul ignore next */
                    internalDebugLog('Auto Switch To Endpoint Error ' + err);
                    if (this.showErrors) {
                        this.error(err, { payload: 'Get Endpoints Request Error' });
                    }
                }
                else {
                    const endpoint = (endpoints || []).find((endpoint) => {
                        endpointMatchForConnecting(endpoint);
                    });
                    if (endpoint && endpoint.endpointUrl != null) {
                        /* istanbul ignore next */
                        internalDebugLog('Auto Switch To Endpoint ' + endpoint.endpointUrl);
                        this.endpoint = endpoint.endpointUrl;
                    }
                    else {
                        internalDebugLog('Auto Switch To Endpoint failed: no valid endpoints');
                    }
                }
                discoverClient.disconnect((err) => {
                    if (err) {
                        /* istanbul ignore next */
                        internalDebugLog('Endpoints Auto Request Error ' + err);
                        if (this.showErrors) {
                            /* istanbul ignore next */
                            this.error(err, { payload: 'Discover Client Disconnect Error' });
                        }
                    }
                    else {
                        internalDebugLog('Endpoints Auto Request Done With Endpoint ' + this.endpoint);
                    }
                });
            });
        };
        const autoSelectEndpointFromConnection = () => {
            internalDebugLog('Auto Searching For Endpoint On ' + this.endpoint);
            if ((0, underscore_1.isUndefined)(this.iiot) || (0, underscore_1.isUndefined)(this.iiot.opcuaClientOptions))
                return;
            let endpointMustExist = this.iiot.opcuaClientOptions.endpointMustExist;
            this.iiot.opcuaClientOptions.endpointMustExist = false;
            let discoverClient = node_opcua_1.OPCUAClient.create(this.iiot.opcuaClientOptions);
            discoverClient.connect(this.endpoint).then(() => {
                internalDebugLog('Auto Searching Endpoint Connected To ' + this.endpoint);
                selectEndpointFromSettings(discoverClient);
                if ((0, underscore_1.isUndefined)(this.iiot) || (0, underscore_1.isUndefined)(this.iiot.opcuaClientOptions))
                    return;
                this.iiot.opcuaClientOptions.endpointMustExist = endpointMustExist;
            }).catch((err) => {
                /* istanbul ignore next */
                internalDebugLog('Get Auto Endpoint Request Error ' + err.message);
                if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot) && !(0, underscore_1.isUndefined)(this.iiot.opcuaClientOptions)) {
                    this.iiot.opcuaClientOptions.endpointMustExist = endpointMustExist;
                }
            });
        };
        /*  #########    SESSION    #########     */
        const startSession = (callerInfo) => __awaiter(this, void 0, void 0, function* () {
            internalDebugLog('Request For New Session From ' + callerInfo);
            if ((0, underscore_1.isUndefined)(this.iiot)) {
                /* istanbul ignore next */
                return;
            }
            if (isInactiveOnOPCUA()) {
                internalDebugLog('State Is Not Active While Start Session-> ' + this.iiot.stateService.state.value);
                if (this.showErrors) {
                    /* istanbul ignore next */
                    this.error(new Error('OPC UA Connector Is Not Active'), { payload: 'Create Session Error' });
                }
                return;
            }
            if (this.iiot.stateService.state.value !== opcua_iiot_core_1.FsmConnectorStates.StateOpened) {
                internalDebugLog('Session Request Not Allowed On State ' + this.iiot.stateService.state.value);
                if (this.showErrors) {
                    /* istanbul ignore next */
                    this.error(new Error('OPC UA Connector Is Not Open'), { payload: 'Create Session Error' });
                }
                return;
            }
            if (!this.iiot.opcuaClient) {
                internalDebugLog('OPC UA Client Connection Is Not Valid On State ' + this.iiot.stateService.state.value);
                if (this.showErrors) {
                    /* istanbul ignore next */
                    this.error(new Error('OPC UA Client Connection Is Not Valid'), { payload: 'Create Session Error' });
                }
                return;
            }
            //this.iiot.stateMachine.sessionrequest()
            this.iiot.stateService.send('SESSIONREQUEST');
            const res = yield this.iiot.opcuaClient.createSession(this.iiot.userIdentity)
                .then((session) => {
                var _a;
                if ((0, underscore_1.isUndefined)(this.iiot))
                    return;
                session.requestedMaxReferencesPerNode = 100000;
                this.iiot.opcuaSession = session;
                //this.iiot.stateMachine.sessionactive()
                this.iiot.stateService.send('SESSIONACTIVATE');
                detailDebugLog('Session Created On ' + this.endpoint + ' For ' + callerInfo);
                opcua_iiot_core_connector_1.default.logSessionInformation(this);
                (_a = this.iiot.opcuaSession) === null || _a === void 0 ? void 0 : _a.on('session_closed', (statusCode) => {
                    handleSessionClose(statusCode);
                });
            }).catch((err) => {
                /* istanbul ignore next */
                if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                    //this.iiot.stateMachine.lock().stopopcua()
                    this.iiot.stateService.send('LOCK');
                    this.iiot.stateService.send('STOP');
                    handleError(err);
                }
                else {
                    internalDebugLog(err.message);
                }
                this.emit('session_error', err);
                return -1;
            });
        });
        const resetBadSession = () => {
            if (!this.iiot) {
                /* istanbul ignore next */
                return;
            }
            this.iiot.sessionNodeRequests += 1;
            detailDebugLog('Session Node Requests At Connector No.: ' + this.iiot.sessionNodeRequests);
            if (this.showErrors) {
                /* istanbul ignore next */
                internalDebugLog('!!!!!!!!!!!!!!!!!!!!!   BAD SESSION ON CONNECTOR   !!!!!!!!!!!!!!!!!!');
            }
            if (this.iiot.sessionNodeRequests > this.maxBadSessionRequests) {
                internalDebugLog('Reset Bad Session Request On State ' + this.iiot.stateService.state.value);
                resetOPCUAConnection('ToManyBadSessionRequests');
            }
        };
        const isInactiveOnOPCUA = () => {
            var _a, _b;
            //let state = this.iiot?.stateMachine?.getMachineState()
            let state = (_b = (_a = this.iiot) === null || _a === void 0 ? void 0 : _a.stateService) === null || _b === void 0 ? void 0 : _b.state.value;
            return (state === opcua_iiot_core_1.FsmConnectorStates.StateStopped ||
                state === opcua_iiot_core_1.FsmConnectorStates.StateEnd ||
                state === opcua_iiot_core_1.FsmConnectorStates.StateRenewed ||
                state === opcua_iiot_core_1.FsmConnectorStates.StateReconfigured);
        };
        const resetOPCUAConnection = (callerInfo) => {
            detailDebugLog(callerInfo + ' Request For New OPC UA Connection');
            if (isInactiveOnOPCUA() || (0, underscore_1.isUndefined)(this.iiot)) {
                return;
            }
            //this.iiot.stateMachine.lock().renew()
            this.iiot.stateService.send('LOCK');
            this.iiot.stateService.send('RENEW');
            this.emit('reset_opcua_connection');
            closeSession(() => {
                renewConnection(() => {
                    detailDebugLog('OPC UA Connection Reset Done');
                });
            });
        };
        const handleError = (err) => {
            internalDebugLog('Handle Error On ' + this.endpoint + ' err: ' + err);
            if (this.showErrors) {
                /* istanbul ignore next */
                this.error(err, { payload: 'Handle Connector Error' });
            }
        };
        const closeSession = (done) => {
            var _a, _b;
            if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
                /* istanbul ignore next */
                done();
                return;
            }
            if (((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.opcuaClient) && ((_b = this.iiot) === null || _b === void 0 ? void 0 : _b.opcuaSession)) {
                detailDebugLog('Close Session And Remove Subscriptions From Session On State ' + this.iiot.stateService.state.value);
                try {
                    this.iiot.opcuaSession.removeAllListeners();
                    this.iiot.opcuaClient.closeSession(this.iiot.opcuaSession, this.iiot.hasOpcUaSubscriptions, (err) => {
                        if (err) {
                            handleError(err);
                        }
                        done();
                    });
                }
                catch (err) {
                    /* istanbul ignore next */
                    handleError(err);
                    done();
                }
                finally {
                    if (this.iiot)
                        this.iiot.opcuaSession = undefined;
                }
            }
            else {
                internalDebugLog('Close Session Without Session On State ' + this.iiot.stateService.state.value);
                done();
            }
        };
        const hasNoSession = () => {
            var _a, _b;
            return underscore_1.default.isUndefined(this.iiot) || underscore_1.default.isUndefined((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.opcuaSession) || underscore_1.default.isNull((_b = this.iiot) === null || _b === void 0 ? void 0 : _b.opcuaSession);
        };
        const hasSession = () => {
            return !hasNoSession();
        };
        const handleSessionClose = (statusCode) => {
            var _a;
            internalDebugLog('Session Closed With StatusCode ' + statusCode);
            if ((0, underscore_1.isUndefined)(this.iiot)) {
                return;
            }
            if (isInactiveOnOPCUA()) {
                detailDebugLog('Connector Is Not Active On OPC UA While Session Close Event');
                return;
            }
            opcua_iiot_core_connector_1.default.logSessionInformation(this);
            if (((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.stateMachine) && this.iiot.stateService.state.value !== opcua_iiot_core_1.FsmConnectorStates.StateSessionRestart) {
                //this.iiot.stateMachine.lock().sessionclose()
                this.iiot.stateService.send('LOCK');
                this.iiot.stateService.send('SESSIONCLOSE');
            }
        };
        const disconnectNodeOPCUA = (done) => {
            var _a;
            if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
                done();
                return;
            }
            internalDebugLog('OPC UA Disconnect Connector On State ' + this.iiot.stateService.state.value);
            if ((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.opcuaClient) {
                internalDebugLog('Close Node Disconnect Connector From ' + this.endpoint);
                try {
                    this.iiot.opcuaClient.disconnect((err) => {
                        if (err) {
                            handleError(err);
                        }
                        internalDebugLog('Close Node Done For Connector On ' + this.endpoint);
                        done();
                    });
                }
                catch (err) {
                    handleError(err);
                    done();
                }
                finally {
                    this.iiot.opcuaClient = undefined;
                }
            }
            else {
                internalDebugLog('Close Node Done For Connector Without Client On ' + this.endpoint);
                done();
            }
        };
        this.on('close', (done) => {
            self.removeAllListeners();
            if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
                done();
                return;
            }
            if (!(0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                done(); // if we have a very fast deploy clicking uer
            }
            else {
                if (isInactiveOnOPCUA()) {
                    detailDebugLog('OPC UA Client Is Not Active On Close Node');
                    (0, opcua_iiot_core_1.resetIiotNode)(this);
                    done();
                }
                else {
                    detailDebugLog('OPC UA Client Is Active On Close Node With State ' + this.iiot.stateService.state.value);
                    if (this.iiot.stateService.state.value === opcua_iiot_core_1.FsmConnectorStates.StateSessionActive) {
                        closeConnector(() => {
                            (0, opcua_iiot_core_1.resetIiotNode)(this);
                            done();
                        });
                    }
                    else {
                        internalDebugLog(this.iiot.stateService.state.value + ' -> !!!  CHECK CONNECTOR STATE ON CLOSE  !!!');
                        (0, opcua_iiot_core_1.resetIiotNode)(this);
                        done();
                    }
                }
            }
        });
        const opcuaDisconnect = (done) => {
            if ((0, underscore_1.isUndefined)(this.iiot) ||
                (0, underscore_1.isUndefined)(this.iiot.registeredNodeList) ||
                underscore_1.default.isEmpty(this.iiot) ||
                underscore_1.default.isArray(this.iiot.registeredNodeList) === false) {
                opcuaDirectDisconnect(done);
                return;
            }
            if (Object.keys(this.iiot.registeredNodeList).length > 0) {
                internalDebugLog('Connector Has Registered Nodes And Can Not Close The Node -> Count: ' + this.iiot.registeredNodeList.length);
                if (disconnectTimeout) {
                    clearTimeout(disconnectTimeout);
                    disconnectTimeout = null;
                }
                disconnectTimeout = setTimeout(() => {
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        closeConnector(done);
                    }
                }, this.connectionStopDelay);
            }
            else {
                opcuaDirectDisconnect(done);
            }
        };
        const opcuaDirectDisconnect = (done) => {
            if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
                done();
                return;
            }
            detailDebugLog('OPC UA Disconnect From Connector ' + this.iiot.stateService.state.value);
            disconnectNodeOPCUA(() => {
                if ((0, underscore_1.isUndefined)(this.iiot)) {
                    done();
                    return;
                }
                //this.iiot.stateMachine.lock().close()
                this.iiot.stateService.send('LOCK');
                this.iiot.stateService.send('CLOSE');
                let fsmState = this.iiot.stateService.state.value;
                detailDebugLog('Disconnected On State ' + fsmState);
                // if (!isInactiveOnOPCUA() && fsmState !== 'CLOSED') { //Todo: check state machine to be closed
                //    return;
                // }
                done();
            });
        };
        const closeConnector = (done) => {
            if ((0, underscore_1.isUndefined)(this.iiot)) {
                return;
            }
            detailDebugLog('Close Connector ' + this.iiot.stateService.value);
            if (isInactiveOnOPCUA()) {
                detailDebugLog('OPC UA Client Is Not Active On Close Connector');
                done();
                return;
            }
            if (this.iiot.opcuaClient) {
                opcuaDisconnect(done);
            }
            else {
                detailDebugLog('OPC UA Client Is Not Valid On Close Connector');
                done();
            }
        };
        const restartWithNewSettings = (config, done) => {
            if ((0, underscore_1.isUndefined)(this.iiot)) {
                return;
            }
            internalDebugLog('Renew With Flex Connector Request On State ' + this.iiot.stateService.state.value);
            //this.iiot.stateMachine.lock().reconfigure()
            this.iiot.stateService.send('LOCK');
            this.iiot.stateService.send('RECONFIGURE');
            updateSettings(config);
            initCertificatesAndKeys();
            renewConnection(done);
        };
        const normalizeCapitalization = (input) => {
            if (!input.length)
                return input;
            return input[0].toUpperCase() + input.substring(1).toLowerCase();
        };
        const updateSettings = (config) => {
            this.discoveryUrl = config.discoveryUrl || this.discoveryUrl;
            this.endpoint = config.endpoint || this.endpoint;
            this.keepSessionAlive = config.keepSessionAlive || this.keepSessionAlive;
            this.securityPolicy = (0, node_opcua_1.coerceSecurityPolicy)(config.securityPolicy || this.securityPolicy);
            this.messageSecurityMode = (0, node_opcua_1.coerceMessageSecurityMode)(normalizeCapitalization((config.securityMode || this.messageSecurityMode)));
            this.name = config.name || this.name;
            this.showErrors = config.showErrors || this.showErrors;
            this.publicCertificateFile = config.publicCertificateFile || this.publicCertificateFile;
            this.privateKeyFile = config.privateKeyFile || this.privateKeyFile;
            this.defaultSecureTokenLifetime = config.defaultSecureTokenLifetime || this.defaultSecureTokenLifetime;
            this.endpointMustExist = config.endpointMustExist || this.endpointMustExist;
            this.autoSelectRightEndpoint = config.autoSelectRightEndpoint || this.autoSelectRightEndpoint;
            this.strategyMaxRetry = config.strategyMaxRetry || this.strategyMaxRetry;
            this.strategyInitialDelay = config.strategyInitialDelay || this.strategyInitialDelay;
            this.strategyMaxDelay = parseInt(config.strategyMaxDelay) || this.strategyMaxDelay;
            this.strategyRandomisationFactor = config.strategyRandomisationFactor || this.strategyRandomisationFactor;
            this.requestedSessionTimeout = config.requestedSessionTimeout || this.requestedSessionTimeout;
            this.connectionStartDelay = config.connectionStartDelay || this.connectionStartDelay;
            this.reconnectDelay = config.reconnectDelay || this.reconnectDelay;
        };
        const resetOPCUAObjects = () => {
            var _a, _b;
            if ((0, underscore_1.isUndefined)(this.iiot)) {
                return;
            }
            detailDebugLog('Reset All OPC UA Objects');
            this.iiot.sessionNodeRequests = 0;
            if (this.iiot.opcuaSession) {
                if (this.iiot.opcuaClient) {
                    this.iiot.opcuaClient.closeSession(this.iiot.opcuaSession, true);
                }
                this.iiot.opcuaSession.removeAllListeners();
                this.iiot.opcuaSession = undefined;
            }
            if (Object.keys(this.iiot.opcuaClient || {}).length > 1) {
                (_a = this.iiot.opcuaClient) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
                (_b = this.iiot.opcuaClient) === null || _b === void 0 ? void 0 : _b.disconnect((err) => {
                    if (err && !(0, underscore_1.isUndefined)(this.iiot)) {
                        handleError(err);
                    }
                });
                this.iiot.opcuaClient = undefined;
            }
        };
        /* #########   FSM   #########     */
        const connectorStateEventFunction = (state) => __awaiter(this, void 0, void 0, function* () {
            var _b, _c;
            if (!state.changed)
                return;
            if (this.iiot === undefined)
                return;
            switch (state.value) {
                case opcua_iiot_core_1.FsmConnectorStates.StateIdle:
                    detailDebugLog('Connector IDLE Event FSM');
                    resetOPCUAObjects();
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateInit:
                    detailDebugLog('Connector Init OPC UA Event FSM');
                    if (!this.iiot) {
                        return;
                    }
                    resetOPCUAObjects();
                    resetAllTimer();
                    this.emit('connector_init', this);
                    initCertificatesAndKeys();
                    if (clientStartTimeout) {
                        clearTimeout(clientStartTimeout);
                        clientStartTimeout = null;
                    }
                    detailDebugLog('connecting OPC UA with delay of msec: ' + this.connectionStartDelay);
                    clientStartTimeout = setTimeout(() => {
                        if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                            try {
                                connectOPCUAEndpoint();
                            }
                            catch (err) {
                                handleError(err);
                                resetOPCUAObjects();
                                //this.iiot.stateMachine.lock().stopopcua()
                                this.iiot.stateService.send('LOCK');
                                this.iiot.stateService.send('STOP');
                            }
                        }
                    }, this.connectionStartDelay);
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateOpened:
                    detailDebugLog('Connector Open Event FSM');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        this.emit('connection_started', this.iiot.opcuaClient, statusHandler);
                        internalDebugLog('Client Connected To ' + this.endpoint);
                        detailDebugLog('Client Options ' + JSON.stringify(this.iiot.opcuaClientOptions));
                        yield startSession('Open Event');
                    }
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateSessionRequested:
                    detailDebugLog('Connector Session Request Event FSM');
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateSessionActive:
                    detailDebugLog('Connector Session Active Event FSM');
                    if (!(0, underscore_1.isUndefined)(this.iiot))
                        this.iiot.sessionNodeRequests = 0;
                    this.emit('session_started', (_b = this.iiot) === null || _b === void 0 ? void 0 : _b.opcuaSession);
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateSessionRestart:
                    detailDebugLog('Connector Session Restart Event FSM');
                    this.emit('session_restart');
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateSessionClosed:
                    detailDebugLog('Connector Session Close Event FSM');
                    this.emit('session_closed');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        this.iiot.opcuaSession = undefined;
                    }
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateClosed:
                    detailDebugLog('Connector Client Close Event FSM');
                    this.emit('connection_closed');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        if (Object.keys(this.iiot.opcuaClient || {}).length > 1) {
                            (_c = this.iiot.opcuaClient) === null || _c === void 0 ? void 0 : _c.disconnect((err) => {
                                if (err) {
                                    handleError(err);
                                }
                            });
                            this.iiot.opcuaClient = undefined;
                        }
                    }
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateLocked:
                    detailDebugLog('Connector Lock Event FSM');
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateUnlocked:
                    detailDebugLog('Connector Unlock Event FSM');
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateStopped:
                    detailDebugLog('Connector Stopped Event FSM');
                    this.emit('connection_stopped');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        resetAllTimer();
                    }
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateEnd:
                    detailDebugLog('Connector End Event FSM');
                    this.emit('connection_end');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        resetAllTimer();
                    }
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateReconfigured:
                    detailDebugLog('Connector Reconfigure Event FSM');
                    this.emit('connection_reconfigure');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        resetAllTimer();
                    }
                    break;
                case opcua_iiot_core_1.FsmConnectorStates.StateRenewed:
                    detailDebugLog('Connector Renew Event FSM');
                    this.emit('connection_renew');
                    if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
                        resetAllTimer();
                    }
                    break;
                default:
                    throw new Error('Connector FSM is not in a valid state');
            }
        });
        this.iiot.stateMachine = opcua_iiot_core_connector_1.default.createConnectorFinalStateMachine();
        this.iiot.stateService = opcua_iiot_core_connector_1.default.startConnectorMachineService(this.iiot.stateMachine);
        this.iiot.stateSubscription = opcua_iiot_core_connector_1.default.subscribeConnectorFSMService(this.iiot.stateService, connectorStateEventFunction);
        const resetAllTimer = () => {
            detailDebugLog('Reset All Timer');
            if (clientStartTimeout) {
                clearTimeout(clientStartTimeout);
                clientStartTimeout = null;
            }
            if (sessionStartTimeout) {
                clearTimeout(sessionStartTimeout);
                sessionStartTimeout = null;
            }
            if (disconnectTimeout) {
                clearTimeout(disconnectTimeout);
                disconnectTimeout = null;
            }
        };
        /*  ---------------------  handle config node behaviour --------------------- */
        this.iiot.registeredNodeList = {};
        const renewFiniteStateMachine = () => {
            if ((0, underscore_1.isUndefined)(this.iiot))
                return;
            this.iiot.stateMachine = null;
            this.iiot.stateService = null;
            this.iiot.stateSubscription = null;
            // @xstate/fsm
            this.iiot.stateMachine = opcua_iiot_core_connector_1.default.createConnectorFinalStateMachine();
            this.iiot.stateService = opcua_iiot_core_connector_1.default.startConnectorMachineService(this.iiot.stateMachine);
            this.iiot.stateSubscription = opcua_iiot_core_connector_1.default.subscribeConnectorFSMService(this.iiot.stateService, connectorStateEventFunction);
        };
        const registerForOPCUA = (opcuaNode, onAlias) => {
            if (!opcuaNode) {
                internalDebugLog('Node Not Valid To Register In Connector');
                return;
            }
            internalDebugLog('Register In Connector NodeId: ' + opcuaNode.id);
            if (!this.iiot || (0, underscore_1.isUndefined)(this.iiot.registeredNodeList)) {
                internalDebugLog('Node Not Initialized With a registeredNodeList To Register In Connector');
                return;
            }
            this.iiot.registeredNodeList[opcuaNode.id] = opcuaNode;
            onAlias('opcua_client_not_ready', () => {
                if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot) && this.iiot.stateService.state.value !== opcua_iiot_core_1.FsmConnectorStates.StateEnd) {
                    resetBadSession();
                }
            });
            if (Object.keys(this.iiot.registeredNodeList).length === 1) {
                internalDebugLog('Start Connector OPC UA Connection');
                renewFiniteStateMachine();
                this.iiot.stateService.send('INITOPCUA');
            }
            else {
            }
        };
        const deregisterForOPCUA = (opcuaNode, done) => {
            if (!opcuaNode) {
                internalDebugLog('Node Not Valid To Deregister In Connector');
                done();
                return;
            }
            opcuaNode.removeAllListeners('opcua_client_not_ready');
            if (!this.iiot || (0, underscore_1.isUndefined)(this.iiot.registeredNodeList)) {
                internalDebugLog('Node Not Initialized With a registeredNodeList To Deregister In Connector');
                return;
            }
            internalDebugLog('Deregister In Connector NodeId: ' + opcuaNode.id);
            delete this.iiot.registeredNodeList[opcuaNode.id];
            if (this.iiot.stateService.state.value === opcua_iiot_core_1.FsmConnectorStates.StateStopped || this.iiot.stateService.state.value === opcua_iiot_core_1.FsmConnectorStates.StateEnd) {
                done();
                return;
            }
            if (Object.keys(this.iiot.registeredNodeList).length === 0) {
                //this.iiot.stateMachine.lock().stopopcua()
                this.iiot.stateService.send('LOCK');
                this.iiot.stateService.send('STOP');
                if (this.iiot.opcuaClient) {
                    detailDebugLog('OPC UA Direct Disconnect On Unregister Of All Nodes');
                    try {
                        this.iiot.opcuaClient.disconnect((err) => {
                            if (err) {
                                handleError(err);
                            }
                            done();
                        });
                    }
                    catch (err) {
                        handleError(err);
                        done();
                    }
                    finally {
                        this.iiot.opcuaClient.removeAllListeners();
                    }
                }
                else {
                    done();
                }
            }
            else {
                done();
            }
        };
        renewFiniteStateMachine();
        this.functions = {
            restartWithNewSettings,
            registerForOPCUA,
            deregisterForOPCUA,
            getUpdatedServerOptions,
            hasNoSession,
            hasSession,
            startSession
        };
        if (process.env.isTest == 'TRUE') {
            this.functions = Object.assign(Object.assign({}, this.functions), { registerForOPCUA,
                connectToClient,
                connectOPCUAEndpoint,
                resetBadSession,
                renewConnection,
                handleError,
                deregisterForOPCUA });
        }
    }
    try {
        RED.nodes.registerType('OPCUA-IIoT-Connector', OPCUAIIoTConnectorConfiguration, {
            credentials: {
                user: { type: 'text' },
                password: { type: 'password' }
            }
        });
    }
    catch (e) {
        internalDebugLog(e.message);
    }
    /*  ---------------------  HTTP Requests --------------------- */
    RED.httpAdmin.get('/opcuaIIoT/client/discover/:id/:discoveryUrl', RED.auth.needsPermission('opcua.discovery'), function (req, res) {
        let node = RED.nodes.getNode(req.params.id);
        let discoverUrlRequest = decodeURIComponent(req.params.discoveryUrl);
        internalDebugLog('Get Discovery Request ' + JSON.stringify(req.params) + ' for ' + discoverUrlRequest);
        if (node) {
            if (discoverUrlRequest && !discoverUrlRequest.includes('opc.tcp://')) {
                res.json([]);
            }
            else {
                (0, node_opcua_1.findServers)(discoverUrlRequest, function (err, results) {
                    if (!err && results) {
                        const endpoints = results.servers.flatMap((server) => server.discoveryUrls);
                        res.json(endpoints);
                    }
                    else {
                        internalDebugLog('Perform Find Servers Request ' + err);
                        if (node.showErrors) {
                            node.error(err, { payload: '' });
                        }
                        res.json([]);
                    }
                });
            }
        }
        else {
            internalDebugLog('Get Discovery Request None Node ' + JSON.stringify(req.params));
            res.json([]);
        }
    });
    RED.httpAdmin.get('/opcuaIIoT/client/endpoints/:id/:endpointUrl', RED.auth.needsPermission('opcua.endpoints'), function (req, res) {
        var _a, _b;
        let node = RED.nodes.getNode(req.params.id);
        let endpointUrlRequest = decodeURIComponent(req.params.endpointUrl);
        internalDebugLog('Get Endpoints Request ' + JSON.stringify(req.params) + ' for ' + endpointUrlRequest);
        if ((0, underscore_1.isUndefined)(node.iiot)) {
            node.error('IIoT invalid');
            return;
        }
        if (node) {
            if (endpointUrlRequest && !endpointUrlRequest.includes('opc.tcp://')) {
                res.json([]);
            }
            else {
                if (!((_a = node.iiot) === null || _a === void 0 ? void 0 : _a.opcuaClientOptions)) {
                    node.iiot.opcuaClientOptions = (_b = node.functions) === null || _b === void 0 ? void 0 : _b.getUpdatedServerOptions();
                }
                let discoveryClient = node_opcua_1.OPCUAClient.create(Object.assign(Object.assign({}, node.iiot.opcuaClientOptions), { endpointMustExist: false }));
                discoveryClient.connect(endpointUrlRequest).then(() => {
                    internalDebugLog('Get Endpoints Connected For Request');
                    discoveryClient.getEndpoints(function (err, endpoints) {
                        if (err) {
                            if (node.showErrors) {
                                node.error(err, { payload: '' });
                            }
                            internalDebugLog('Get Endpoints Request Error ' + err);
                            res.json([]);
                        }
                        else {
                            internalDebugLog('Sending Endpoints For Request');
                            res.json(endpoints);
                        }
                        discoveryClient.disconnect(() => {
                            internalDebugLog('Get Endpoints Request Disconnect');
                        });
                    });
                }).catch(function (err) {
                    internalDebugLog('Get Endpoints Request Error ' + err.message);
                    res.json([]);
                });
            }
        }
        else {
            internalDebugLog('Get Endpoints Request None Node ' + JSON.stringify(req.params));
            res.json([]);
        }
    });
    RED.httpAdmin.get('/opcuaIIoT/plain/DataTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.datatypes'), function (req, res) {
        res.json(underscore_1.default.toArray(underscore_1.default.invert(node_opcua_1.DataTypeIds)));
    });
    RED.httpAdmin.get('/opcuaIIoT/plain/AttributeIds', RED.auth.needsPermission('opcuaIIoT.plain.attributeids'), function (req, res) {
        res.json(underscore_1.default.toArray(underscore_1.default.invert(node_opcua_1.AttributeIds)));
    });
    RED.httpAdmin.get('/opcuaIIoT/plain/StatusCodes', RED.auth.needsPermission('opcuaIIoT.plain.statuscodes'), function (req, res) {
        res.json(underscore_1.default.toArray(underscore_1.default.invert(node_opcua_1.StatusCodes)));
    });
    RED.httpAdmin.get('/opcuaIIoT/plain/ObjectTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.objecttypeids'), function (req, res) {
        res.json(node_opcua_1.ObjectTypeIds);
    });
    RED.httpAdmin.get('/opcuaIIoT/plain/VariableTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.variabletypeids'), function (req, res) {
        res.json(node_opcua_1.VariableTypeIds);
    });
    RED.httpAdmin.get('/opcuaIIoT/plain/ReferenceTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.referencetypeids'), function (req, res) {
        res.json(node_opcua_1.ReferenceTypeIds);
    });
    RED.httpAdmin.get('/opcuaIIoT/xmlsets/public', RED.auth.needsPermission('opcuaIIoT.xmlsets'), function (req, res) {
        const xmlset = [
            node_opcua_1.nodesets.di,
            node_opcua_1.nodesets.adi,
            'public/vendor/opc-foundation/xml/Opc.ISA95.NodeSet2.xml',
            'public/vendor/opc-foundation/xml/Opc.Ua.Adi.NodeSet2.xml',
            'public/vendor/opc-foundation/xml/Opc.Ua.Di.NodeSet2.xml',
            'public/vendor/opc-foundation/xml/Opc.Ua.Gds.NodeSet2.xml',
            'public/vendor/harting/10_di.xml',
            'public/vendor/harting/20_autoid.xml',
            'public/vendor/harting/30_aim.xml',
        ];
        res.json(xmlset);
    });
    RED.httpAdmin.get('/opcuaIIoT/list/DataTypeIds', RED.auth.needsPermission('opcuaIIoT.list.datatypeids'), function (req, res) {
        const resultTypeList = enumToTypeList(node_opcua_1.DataTypeIds);
        res.json(resultTypeList);
    });
    RED.httpAdmin.get('/opcuaIIoT/list/EventTypeIds', RED.auth.needsPermission('opcuaIIoT.list.eventtypeids'), function (req, res) {
        const eventTypesResults = enumToTypeList(node_opcua_1.ObjectTypeIds).filter((item) => {
            return item.label.indexOf('Event') > -1;
        });
        res.json(eventTypesResults);
    });
    RED.httpAdmin.get('/opcuaIIoT/list/InstanceTypeIds', RED.auth.needsPermission('opcuaIIoT.list.instancetypeids'), function (req, res) {
        const resultTypeList = [node_opcua_1.ObjectTypeIds, node_opcua_1.VariableTypeIds].flatMap((item) => enumToTypeList(item));
        res.json(resultTypeList);
    });
    const enumToTypeList = (inputEnum) => {
        return (0, helpers_1.getEnumKeys)(inputEnum).map((key) => {
            return { nodeId: `i=${inputEnum[key]}`, label: key };
        });
    };
    RED.httpAdmin.get('/opcuaIIoT/list/VariableTypeIds', RED.auth.needsPermission('opcuaIIoT.list.variabletypeids'), function (req, res) {
        const resultTypeList = enumToTypeList(node_opcua_1.VariableTypeIds);
        res.json(resultTypeList);
    });
    RED.httpAdmin.get('/opcuaIIoT/list/ReferenceTypeIds', RED.auth.needsPermission('opcuaIIoT.list.referencetypeids'), function (req, res) {
        const resultTypeList = enumToTypeList(node_opcua_1.ReferenceTypeIds);
        res.json(resultTypeList);
    });
    RED.httpAdmin.get('/opcuaIIoT/list/FilterTypes', RED.auth.needsPermission('opcuaIIoT.list.filterids'), function (req, res) {
        const resultTypeList = [
            { name: 'dataType', label: 'Data Type' },
            { name: 'dataValue', label: 'Data Value' },
            { name: 'nodeClass', label: 'Node Class' },
            { name: 'typeDefinition', label: 'Type Definition' },
            { name: 'browseName', label: 'Browse Name' },
            { name: 'nodeId', label: 'Node Id' },
        ];
        res.json(resultTypeList);
    });
};

//# sourceMappingURL=opcua-iiot-connector.js.map