Skip to content

Commit

Permalink
Bump 6.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanBluefox committed Dec 28, 2024
1 parent d537528 commit d47e1ea
Show file tree
Hide file tree
Showing 8 changed files with 626 additions and 327 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Instantiate the server with Node-RED
-->

## Changelog
### **WORK IN PROGRESS**

- (@GermanBluefox) Restart node-red if admin settings changed

### 6.0.1 (2024-09-30)

- (@GermanBluefox) Corrected the case if `envVars` settings is undefined
Expand Down
2 changes: 1 addition & 1 deletion admin/jsonConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"_general": {
"type": "panel",
"label": "General",
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuNC4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIzIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNNDk1LjkgMTY2LjZjMy4yIDguNyAuNSAxOC40LTYuNCAyNC42bC00My4zIDM5LjRjMS4xIDguMyAxLjcgMTYuOCAxLjcgMjUuNHMtLjYgMTcuMS0xLjcgMjUuNGw0My4zIDM5LjRjNi45IDYuMiA5LjYgMTUuOSA2LjQgMjQuNmMtNC40IDExLjktOS43IDIzLjMtMTUuOCAzNC4zbC00LjcgOC4xYy02LjYgMTEtMTQgMjEuNC0yMi4xIDMxLjJjLTUuOSA3LjItMTUuNyA5LjYtMjQuNSA2LjhsLTU1LjctMTcuN2MtMTMuNCAxMC4zLTI4LjIgMTguOS00NCAyNS40bC0xMi41IDU3LjFjLTIgOS4xLTkgMTYuMy0xOC4yIDE3LjhjLTEzLjggMi4zLTI4IDMuNS00Mi41IDMuNXMtMjguNy0xLjItNDIuNS0zLjVjLTkuMi0xLjUtMTYuMi04LjctMTguMi0xNy44bC0xMi41LTU3LjFjLTE1LjgtNi41LTMwLjYtMTUuMS00NC0yNS40TDgzLjEgNDI1LjljLTguOCAyLjgtMTguNiAuMy0yNC41LTYuOGMtOC4xLTkuOC0xNS41LTIwLjItMjIuMS0zMS4ybC00LjctOC4xYy02LjEtMTEtMTEuNC0yMi40LTE1LjgtMzQuM2MtMy4yLTguNy0uNS0xOC40IDYuNC0yNC42bDQzLjMtMzkuNEM2NC42IDI3My4xIDY0IDI2NC42IDY0IDI1NnMuNi0xNy4xIDEuNy0yNS40TDIyLjQgMTkxLjJjLTYuOS02LjItOS42LTE1LjktNi40LTI0LjZjNC40LTExLjkgOS43LTIzLjMgMTUuOC0zNC4zbDQuNy04LjFjNi42LTExIDE0LTIxLjQgMjIuMS0zMS4yYzUuOS03LjIgMTUuNy05LjYgMjQuNS02LjhsNTUuNyAxNy43YzEzLjQtMTAuMyAyOC4yLTE4LjkgNDQtMjUuNGwxMi41LTU3LjFjMi05LjEgOS0xNi4zIDE4LjItMTcuOEMyMjcuMyAxLjIgMjQxLjUgMCAyNTYgMHMyOC43IDEuMiA0Mi41IDMuNWM5LjIgMS41IDE2LjIgOC43IDE4LjIgMTcuOGwxMi41IDU3LjFjMTUuOCA2LjUgMzAuNiAxNS4xIDQ0IDI1LjRsNTUuNy0xNy43YzguOC0yLjggMTguNi0uMyAyNC41IDYuOGM4LjEgOS44IDE1LjUgMjAuMiAyMi4xIDMxLjJsNC43IDguMWM2LjEgMTEgMTEuNCAyMi40IDE1LjggMzQuM3pNMjU2IDMzNmE4MCA4MCAwIDEgMCAwLTE2MCA4MCA4MCAwIDEgMCAwIDE2MHoiLz48L3N2Zz4=",
"icon": "data:image/svg+xml;base64,PHN2ZydefaultPermissionsB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuNC4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIzIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNNDk1LjkgMTY2LjZjMy4yIDguNyAuNSAxOC40LTYuNCAyNC42bC00My4zIDM5LjRjMS4xIDguMyAxLjcgMTYuOCAxLjcgMjUuNHMtLjYgMTcuMS0xLjcgMjUuNGw0My4zIDM5LjRjNi45IDYuMiA5LjYgMTUuOSA2LjQgMjQuNmMtNC40IDExLjktOS43IDIzLjMtMTUuOCAzNC4zbC00LjcgOC4xYy02LjYgMTEtMTQgMjEuNC0yMi4xIDMxLjJjLTUuOSA3LjItMTUuNyA5LjYtMjQuNSA2LjhsLTU1LjctMTcuN2MtMTMuNCAxMC4zLTI4LjIgMTguOS00NCAyNS40bC0xMi41IDU3LjFjLTIgOS4xLTkgMTYuMy0xOC4yIDE3LjhjLTEzLjggMi4zLTI4IDMuNS00Mi41IDMuNXMtMjguNy0xLjItNDIuNS0zLjVjLTkuMi0xLjUtMTYuMi04LjctMTguMi0xNy44bC0xMi41LTU3LjFjLTE1LjgtNi41LTMwLjYtMTUuMS00NC0yNS40TDgzLjEgNDI1LjljLTguOCAyLjgtMTguNiAuMy0yNC41LTYuOGMtOC4xLTkuOC0xNS41LTIwLjItMjIuMS0zMS4ybC00LjctOC4xYy02LjEtMTEtMTEuNC0yMi40LTE1LjgtMzQuM2MtMy4yLTguNy0uNS0xOC40IDYuNC0yNC42bDQzLjMtMzkuNEM2NC42IDI3My4xIDY0IDI2NC42IDY0IDI1NnMuNi0xNy4xIDEuNy0yNS40TDIyLjQgMTkxLjJjLTYuOS02LjItOS42LTE1LjktNi40LTI0LjZjNC40LTExLjkgOS43LTIzLjMgMTUuOC0zNC4zbDQuNy04LjFjNi42LTExIDE0LTIxLjQgMjIuMS0zMS4yYzUuOS03LjIgMTUuNy05LjYgMjQuNS02LjhsNTUuNyAxNy43YzEzLjQtMTAuMyAyOC4yLTE4LjkgNDQtMjUuNGwxMi41LTU3LjFjMi05LjEgOS0xNi4zIDE4LjItMTcuOEMyMjcuMyAxLjIgMjQxLjUgMCAyNTYgMHMyOC43IDEuMiA0Mi41IDMuNWM5LjIgMS41IDE2LjIgOC43IDE4LjIgMTcuOGwxMi41IDU3LjFjMTUuOCA2LjUgMzAuNiAxNS4xIDQ0IDI1LjRsNTUuNy0xNy43YzguOC0yLjggMTguNi0uMyAyNC41IDYuOGM4LjEgOS44IDE1LjUgMjAuMiAyMi4xIDMxLjJsNC43IDguMWM2LjEgMTEgMTEuNCAyMi40IDE1LjggMzQuM3pNMjU2IDMzNmE4MCA4MCAwIDEgMCAwLTE2MCA4MCA4MCAwIDEgMCAwIDE2MHoiLz48L3N2Zz4=",
"items": {
"bind": {
"newLine": true,
Expand Down
34 changes: 26 additions & 8 deletions lib/adapter-config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@
// using the actual properties present in io-package.json
// in order to provide typings for adapter.config properties

import { type native } from '../io-package.json';

type _AdapterConfig = Partial<typeof native>;

// Augment the globally declared type ioBroker.AdapterConfig
declare global {
namespace ioBroker {
// tslint:disable-next-line:no-empty-interface
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface AdapterConfig extends _AdapterConfig {
// Do not enter anything here!
interface AdapterConfig {
bind: string;
port: number | string;
secure: boolean;
certPublic: string;
certPrivate: string;
httpAdminRoot: string;
httpNodeRoot: string;
httpStatic: string;
npmLibs: [];
maxMemory: number;
valueConvert: boolean;
palletmanagerEnabled: boolean;
projectsEnabled: boolean;
allowCreationOfForeignObjects: boolean;
safeMode: boolean;
doNotReadObjectsDynamically: boolean;
authType: 'None' | 'Simple' | 'Extended';
user: string;
pass: string;
hasDefaultPermissions: boolean;
defaultPermissions: string;
authExt: { username: string; password: string; permissions: string }[];
editor: 'monaco' | 'ace';
theme: string;
envVars: { name: string; value: string }[];
}
}
}
149 changes: 98 additions & 51 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const utils = require('@iobroker/adapter-core');
const { Adapter, getAbsoluteDefaultDataDir } = require('@iobroker/adapter-core');
const fs = require('node:fs');
const path = require('node:path');
const spawn = require('node:child_process').spawn;
Expand Down Expand Up @@ -39,7 +39,7 @@ function getNodeRedEditorPath() {
const nodePath = getNodeRedPath();
const editorClientPath = getNodeRedEditorPath();

class NodeRed extends utils.Adapter {
class NodeRed extends Adapter {
constructor(options) {
super({
...options,
Expand All @@ -50,6 +50,7 @@ class NodeRed extends utils.Adapter {
this.systemSecret = null;
this.userDataDir = `${__dirname}/userdata/`;
this.redProcess = null;
this.adminUrl = '';

this.stopping = false;
this.saveTimer = null;
Expand All @@ -60,6 +61,7 @@ class NodeRed extends utils.Adapter {
this.attempts = {};
this.additional = [];

this.on('objectChange', this.onObjectChange.bind(this));
this.on('ready', this.onReady.bind(this));
//this.on('stateChange', this.onStateChange.bind(this));
this.on('message', this.onMessage.bind(this));
Expand Down Expand Up @@ -138,6 +140,75 @@ class NodeRed extends utils.Adapter {
})};`;
}

async onObjectChange(id) {
if (id.startsWith('system.adapter.admin.')) {
const { adminInstanceObj, adminUrl } = await this.getWsConnectionString();
if (this.adminUrl !== adminUrl) {
// restart node-red to apply new settings
this.log.info('Restarting node-red to apply new settings of admin instance');
const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
if (obj) {
await this.setForeignObjectAsync(obj._id, obj);
}
}
}
}

async getWsConnectionString() {
// get settings for admin
const settings = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
let adminInstanceObj;
let adminUrl = '';

if (settings) {
// read all admin adapters on this host
const admins = await this.getObjectViewAsync(
'system',
'instance',
{startkey: 'system.adapter.admin.', endkey: 'system.adapter.admin.\u9999'},
{},
);
let admin = admins.rows.find(
obj =>
// admin should run on the same host
obj.value.common.host === settings.common.host &&
// admin should not have authentication
!obj.value.native.auth &&
// admin should be enabled
obj.value.common.enabled &&
// admin should have secure enabled if node-red has secure not enabled
((!obj.value.native.secure && !!settings.native.secure) ||
// or admin should have the same secure settings
!!obj.value.native.secure === !!settings.native.secure),
);
adminInstanceObj = admin?.value || null;
if (adminInstanceObj) {
// subscribe on changes of admin instance
await this.subscribeForeignObjectsAsync(adminInstanceObj._id);
}
if (this.config.doNotReadObjectsDynamically) {
adminUrl = '';
} else if (adminInstanceObj && !adminInstanceObj.native.auth) {
if (
(!adminInstanceObj.native.secure && !!settings.native.secure) ||
!!adminInstanceObj.native.secure === !!settings.native.secure
) {
adminUrl = `ws${adminInstanceObj.native.secure ? 's' : ''}://${adminInstanceObj.native.bind === '0.0.0.0' || adminInstanceObj.native.bind === '127.0.0.1' ? `' + window.location.hostname + '` : adminInstanceObj.native.bind}:${adminInstanceObj.native.port}`;
` var socket = new WebSocket('ws${adminInstanceObj.native.secure ? 's' : ''}://${adminInstanceObj.native.bind === '0.0.0.0' || adminInstanceObj.native.bind === '127.0.0.1' ? `' + window.location.hostname + '` : adminInstanceObj.native.bind}:${adminInstanceObj.native.port}?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN`;
} else {
adminUrl = '';
}
} else if (adminInstanceObj) {
adminUrl = '';

} else {
adminUrl = '';
}
}

return { adminInstanceObj, adminUrl };
}

async generateHtml() {
const searchText = '// THIS LINE WILL BE CHANGED FOR ADMIN';
const html = fs.readFileSync(`${__dirname}/nodes/ioBroker.html`).toString('utf8');
Expand All @@ -146,59 +217,35 @@ class NodeRed extends utils.Adapter {
if (pos) {
this.log.debug(`Found searched text "${searchText}" of /nodes/ioBroker.html in line ${pos + 1}`);

// get settings for admin
const settings = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
if (settings) {
// read all admin adapters on this host
const admins = await this.getObjectViewAsync(
'system',
'instance',
{ startkey: 'system.adapter.admin.', endkey: 'system.adapter.admin.\u9999' },
{},
);
let admin = admins.rows.find(
obj =>
obj.value.common.host === settings.common.host &&
!obj.value.native.auth &&
obj.value.common.enabled &&
((!obj.value.native.secure && !!settings.native.secure) ||
!!obj.value.native.secure === !!settings.native.secure),
);
const adminInstanceObj = admin ? admin.value : null;

if (this.config.doNotReadObjectsDynamically) {
lines[pos] = ` var socket = null; ${searchText}`;
} else if (adminInstanceObj && !adminInstanceObj.native.auth) {
if (
(!adminInstanceObj.native.secure && !!settings.native.secure) ||
!!adminInstanceObj.native.secure === !!settings.native.secure
) {
lines[pos] =
` var socket = new WebSocket('ws${adminInstanceObj.native.secure ? 's' : ''}://${adminInstanceObj.native.bind === '0.0.0.0' || adminInstanceObj.native.bind === '127.0.0.1' ? `' + window.location.hostname + '` : adminInstanceObj.native.bind}:${adminInstanceObj.native.port}?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN`;
} else {
lines[pos] = ` var socket = null; ${searchText}`;
this.log.warn(
`Cannot enable the dynamic object read as admin is SSL ${adminInstanceObj.native.secure ? 'with' : 'without'} and node-red is ${settings.native.secure ? 'with' : 'without'} SSL`,
);
}
} else if (adminInstanceObj) {
lines[pos] = ` var socket = null; ${searchText}`;
this.log.warn(`Cannot enable the dynamic object read as admin has authentication`);
const { adminInstanceObj, adminUrl } = await this.getWsConnectionString();

if (this.config.doNotReadObjectsDynamically) {
lines[pos] = ` var socket = null; ${searchText}`;
} else if (adminInstanceObj) {
if (adminUrl) {
lines[pos] =
` var socket = new WebSocket('${adminUrl}?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN`;
} else {
lines[pos] = ` var socket = null; ${searchText}`;
this.log.warn(`Cannot enable the dynamic object read as admin has authentication`);
this.log.warn(
`Cannot enable the dynamic object read as admin is SSL ${adminInstanceObj.native.secure ? 'with' : 'without'} and node-red is ${this.config.secure ? 'with' : 'without'} SSL`,
);
}
} else {
lines[pos] = ` var socket = null; ${searchText}`;
this.log.warn(`Cannot enable the dynamic object read as no admin instance found on the same host and wihout authentication`);
}

const searchTextIob = '// THIS LINE WILL BE CHANGED FOR SELECT ID';
const posIob = lines.findIndex(line => line.includes(searchTextIob));
if (posIob !== -1 && adminInstanceObj) {
lines[posIob] = ` ${NodeRed.getAdminJson(adminInstanceObj)} ${searchTextIob}`;
}
const searchTextIob = '// THIS LINE WILL BE CHANGED FOR SELECT ID';
const posIob = lines.findIndex(line => line.includes(searchTextIob));
if (posIob !== -1 && adminInstanceObj) {
lines[posIob] = ` ${NodeRed.getAdminJson(adminInstanceObj)} ${searchTextIob}`;
}

if (html !== lines.join('\n')) {
fs.writeFileSync(`${__dirname}/nodes/ioBroker.html`, lines.join('\n'));
}
if (html !== lines.join('\n')) {
fs.writeFileSync(`${__dirname}/nodes/ioBroker.html`, lines.join('\n'));
}
this.adminUrl = adminUrl;
}
}

Expand Down Expand Up @@ -403,9 +450,9 @@ class NodeRed extends utils.Adapter {

// Find userdata directory
if (this.instance === 0) {
this.userDataDir = path.join(utils.getAbsoluteDefaultDataDir(), 'node-red');
this.userDataDir = path.join(getAbsoluteDefaultDataDir(), 'node-red');
} else {
this.userDataDir = path.join(utils.getAbsoluteDefaultDataDir(), `node-red.${this.instance}`);
this.userDataDir = path.join(getAbsoluteDefaultDataDir(), `node-red.${this.instance}`);
}

if (this.config.npmLibs && !this.config.palletmanagerEnabled) {
Expand Down
21 changes: 12 additions & 9 deletions nodes/ioBroker.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
<script type="text/javascript" src="selectID.js"></script>

<script type="text/javascript">
window.ioBrokerAdmin = {"port":8081,"host":"","protocol":"http:"}; // THIS LINE WILL BE CHANGED FOR SELECT ID
window.ioBrokerAdmin = {"port":8081,"host":"","protocol":"https:"}; // THIS LINE WILL BE CHANGED FOR SELECT ID

function readData() {
return new Promise((resolve, reject) => {
console.log('Connecting...');
var socket = new WebSocket('ws://' + window.location.hostname + ':8081?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN
var socket = new WebSocket('wss://' + window.location.hostname + ':8081?sid=' + Date.now()); // THIS LINE WILL BE CHANGED FOR ADMIN

if (!socket) {
console.warn('Authentication must be disabled to support the objects read!');
Expand All @@ -44,7 +44,7 @@
return reject();
}

var timeout = setTimeout(function () {
let timeout = setTimeout(function () {
window.__ioBrokerTimeout = true;
reject && reject('timeout');
reject = null;
Expand All @@ -60,16 +60,17 @@
socket.onopen = function () {
console.log('Connected to admin successfully!');
};

socket.onmessage = function (event) {
if (event.data.indexOf('___ready___') !== -1) {
if (event.data.includes('___ready___')) {
setTimeout(function () {
socket.send('[3,2,"getObjects",[]]');
}, 50);
} else if (resolve && event.data.indexOf('getObjects') !== -1) {
} else if (resolve && event.data.includes('getObjects')) {
clearTimeout(timeout);
timeout = null;
try {
var data = JSON.parse(event.data)[3][1];
const data = JSON.parse(event.data)[3][1];
console.log(`Could read dynamic data from admin!`);
resolve(data);
} catch (e) {
Expand All @@ -90,9 +91,11 @@
socket.onerror = function (error) {
if (resolve) {
window.__ioBrokerTimeout = true;
console.error('Error on connection: ' + error.message);
reject && reject(error.message);
reject = null;
console.error(`Error on connection: ${error.message}`);
if (reject) {
reject(error.message);
reject = null;
}
resolve = null;

try {
Expand Down
2 changes: 1 addition & 1 deletion nodes/ioBroker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = function (RED) {
require('events').EventEmitter.prototype._maxListeners = 10000;

const utils = require('@iobroker/adapter-core');
const settings = require(process.env.NODE_RED_HOME + '/lib/red').settings;
const settings = require(`${process.env.NODE_RED_HOME}/lib/red`).settings;

const instance = settings.get('iobrokerInstance') || 0;
let config = settings.get('iobrokerConfig');
Expand Down
Loading

0 comments on commit d47e1ea

Please sign in to comment.