[jsscripting] Wrap UI scripts, make dependency tracking configurable & config refactorings (#19019)

* [jsscripting] Add engineIdentifier to logging on OpenhabGraalJSScriptEngine

Signed-off-by: Florian Hotze <dev@florianhotze.com>
This commit is contained in:
Florian Hotze
2025-07-23 22:14:32 +02:00
committed by GitHub
parent e458413289
commit 18d288b679
9 changed files with 280 additions and 91 deletions
@@ -10,28 +10,20 @@ to common openHAB functionality within rules including items, things, actions, l
## Configuration
This add-on includes by default the [openhab-js](https://github.com/openhab/openhab-js/) NPM library and exports its namespaces onto the global namespace.
This add-on includes a version of the [openhab-js](https://github.com/openhab/openhab-js/) NPM library.
This allows the use of `items`, `actions`, `cache` and other objects without the need to explicitly import them using `require()`.
This functionality can be disabled for users who prefer to manage their own imports via the add-on configuration options.
Depending on the add-on configuration, it automatically exports all namespaces (see [Standard Library](#standard-library)) onto the global namespace.
This allows the use of `items`, `actions`, `cache` and other APIs from the UI without the need to explicitly import them using `require()`.
This functionality can be disabled for users who prefer to manage their own imports via the add-on configuration options:
By default, the injection of the [openhab-js](https://github.com/openhab/openhab-js/) NPM library is cached (using a special mechanism instead of `require()`) to improve performance and reduce memory usage.
- Only inject the openhab-js namespaces globally for UI-based rules and scripts (recommended and default).
- Inject the openhab-js namespaces globally for all scripts, including file-based rules and transformations.
- Disable injection of the openhab-js namespaces everywhere, which means you need to import the library using `require()` in every script that uses it.
When configuring the add-on, you should ask yourself these questions:
If enabled, the injection of the [openhab-js](https://github.com/openhab/openhab-js/) NPM library is cached (using a special mechanism instead of `require()`) to improve script loading performance.
This can be disabled, which will allow you to use a different version of the library than the one included in the add-on.
1. Do I want to have the openhab-js namespaces automatically globally available (`injectionEnabled`)?
- Yes: "Use Built-In Variables" (default)
- No: "Do Not Use Built-In Variables", which will allow you to decide what to import and really speed up script loading, but you need to manually import the library, which actually will slow down script loading again.
2. Do I want to have a different version injected other than the included one (`injectionCachingEnabled`)?
- Yes: "Do Not Cache Library Injection" and install your version to the `$OPENHAB_CONF/automation/js/node_modules` folder, which will slow down script loading, because the injection is not cached.
- No: "Cache Library Injection" (default), which will speed up the initial loading of a script because the library's injection is cached.
Note that in case you disable caching or your code uses `require()` to import the library and there is no installation of the library found in the node_modules folder, the add-on will fallback to its included version.
In general, the first run of a script will take longer than the subsequent runs.
This is because on the first run both the globals (like `console`) and (if enabled) the library are injected into the script's context.
<!-- Paste the copied docs from openhab-js under this comment. Do NOT forget the table of contents. -->
<!-- Paste the copied docs from openhab-js under this comment. -->
### UI Based Rules
@@ -12,6 +12,7 @@
*/
package org.openhab.automation.jsscripting.internal;
import static org.openhab.core.automation.module.script.ScriptEngineFactory.CONTEXT_KEY_ENGINE_IDENTIFIER;
import static org.openhab.core.automation.module.script.ScriptTransformationService.OPENHAB_TRANSFORMATION_SCRIPT;
import java.util.Arrays;
@@ -98,7 +99,7 @@ class DebuggingGraalScriptEngine<T extends ScriptEngine & Invocable & AutoClosea
ScriptContext ctx = delegate.getContext();
Object fileName = ctx.getAttribute("javax.script.filename");
Object ruleUID = ctx.getAttribute("ruleUID");
Object ohEngineIdentifier = ctx.getAttribute("oh.engine-identifier");
Object ohEngineIdentifier = ctx.getAttribute(CONTEXT_KEY_ENGINE_IDENTIFIER);
String identifier = "stack";
if (fileName != null) {
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.automation.jsscripting.internal;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.ConfigParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes JavaScript Configuration Parameters.
*
* @author Florian Hotze - Initial contribution
*/
@NonNullByDefault
public class GraalJSScriptEngineConfiguration {
Logger logger = LoggerFactory.getLogger(GraalJSScriptEngineConfiguration.class);
private static final String CFG_INJECTION_ENABLED = "injectionEnabledV2";
private static final String CFG_INJECTION_CACHING_ENABLED = "injectionCachingEnabled";
private static final String CFG_WRAPPER_ENABLED = "wrapperEnabled";
private static final String CFG_DEPENDENCY_TRACKING_ENABLED = "dependencyTrackingEnabled";
public static final int INJECTION_DISABLED = 0;
public static final int INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY = 2;
public static final int INJECTION_ENABLED_FOR_ALL_SCRIPTS = 1;
private int injectionEnabled = 2;
private boolean injectionCachingEnabled = true;
private boolean wrapperEnabled = true;
private boolean dependencyTrackingEnabled = true;
/**
* Create a new configuration instance from the given parameters.
*
* @param config configuration parameters to apply to JavaScript
*/
public GraalJSScriptEngineConfiguration(Map<String, ?> config) {
update(config);
}
/**
* To be called when the configuration is modified.
*
* @param config configuration parameters to apply to JavaScript
*/
void modified(Map<String, ?> config) {
boolean oldDependencyTrackingEnabled = dependencyTrackingEnabled;
boolean oldWrapperEnabled = wrapperEnabled;
this.update(config);
if (oldDependencyTrackingEnabled != dependencyTrackingEnabled) {
logger.info(
"{} dependency tracking for JavaScript Scripting. Please resave your scripts to apply this change.",
dependencyTrackingEnabled ? "Enabled" : "Disabled");
}
if (oldWrapperEnabled != wrapperEnabled) {
logger.info(
"{} wrapper for JavaScript Scripting. Please resave your UI-based scripts to apply this change.",
wrapperEnabled ? "Enabled" : "Disabled");
}
}
/**
* Update configuration
*
* @param config configuration parameters to apply to JavaScript
*/
private void update(Map<String, ?> config) {
logger.trace("JavaScript Script Engine Configuration: {}", config);
injectionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_ENABLED), Integer.class,
INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY);
injectionCachingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_CACHING_ENABLED), Boolean.class,
true);
wrapperEnabled = ConfigParser.valueAsOrElse(config.get(CFG_WRAPPER_ENABLED), Boolean.class, true);
dependencyTrackingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_DEPENDENCY_TRACKING_ENABLED),
Boolean.class, true);
}
public boolean isInjection(int type) {
return type == injectionEnabled;
}
public boolean isInjectionCachingEnabled() {
return injectionCachingEnabled;
}
public boolean isWrapperEnabled() {
return wrapperEnabled;
}
public boolean isDependencyTrackingEnabled() {
return dependencyTrackingEnabled;
}
}
@@ -26,13 +26,14 @@ import org.openhab.automation.jsscripting.internal.fs.watch.JSDependencyTracker;
import org.openhab.core.OpenHAB;
import org.openhab.core.automation.module.script.ScriptDependencyTracker;
import org.openhab.core.automation.module.script.ScriptEngineFactory;
import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.config.core.ConfigurableService;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.oracle.truffle.js.scriptengine.GraalJSEngineFactory;
@@ -46,18 +47,14 @@ import com.oracle.truffle.js.scriptengine.GraalJSEngineFactory;
+ "=org.openhab.jsscripting")
@ConfigurableService(category = "automation", label = "JS Scripting", description_uri = "automation:jsscripting")
@NonNullByDefault
public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
public class GraalJSScriptEngineFactory implements ScriptEngineFactory {
public static final Path JS_DEFAULT_PATH = Paths.get(OpenHAB.getConfigFolder(), "automation", "js");
public static final String NODE_DIR = "node_modules";
public static final Path JS_LIB_PATH = JS_DEFAULT_PATH.resolve(NODE_DIR);
public static final String SCRIPT_TYPE = "application/javascript";
private static final String CFG_INJECTION_ENABLED = "injectionEnabled";
private static final String CFG_INJECTION_CACHING_ENABLED = "injectionCachingEnabled";
private static final GraalJSEngineFactory factory = new GraalJSEngineFactory();
private static final List<String> scriptTypes = createScriptTypes();
private static List<String> createScriptTypes() {
@@ -67,8 +64,8 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
.flatMap(List::stream).distinct().toList();
}
private boolean injectionEnabled = true;
private boolean injectionCachingEnabled = true;
private final Logger logger = LoggerFactory.getLogger(GraalJSScriptEngineFactory.class);
private final GraalJSScriptEngineConfiguration configuration;
private final JSScriptServiceUtil jsScriptServiceUtil;
private final JSDependencyTracker jsDependencyTracker;
@@ -76,9 +73,16 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
@Activate
public GraalJSScriptEngineFactory(final @Reference JSScriptServiceUtil jsScriptServiceUtil,
final @Reference JSDependencyTracker jsDependencyTracker, Map<String, Object> config) {
logger.debug("Loading GraalJSScriptEngineFactory");
this.jsDependencyTracker = jsDependencyTracker;
this.jsScriptServiceUtil = jsScriptServiceUtil;
modified(config);
this.configuration = new GraalJSScriptEngineConfiguration(config);
}
@Modified
protected void modified(Map<String, ?> config) {
configuration.modified(config);
}
@Override
@@ -96,19 +100,12 @@ public final class GraalJSScriptEngineFactory implements ScriptEngineFactory {
if (!scriptTypes.contains(scriptType)) {
return null;
}
return new DebuggingGraalScriptEngine<>(new OpenhabGraalJSScriptEngine(injectionEnabled,
injectionCachingEnabled, jsScriptServiceUtil, jsDependencyTracker));
return new DebuggingGraalScriptEngine<>(
new OpenhabGraalJSScriptEngine(configuration, jsScriptServiceUtil, jsDependencyTracker));
}
@Override
public @Nullable ScriptDependencyTracker getDependencyTracker() {
return jsDependencyTracker;
}
@Modified
protected void modified(Map<String, ?> config) {
this.injectionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_ENABLED), Boolean.class, true);
this.injectionCachingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_CACHING_ENABLED),
Boolean.class, true);
}
}
@@ -13,6 +13,7 @@
package org.openhab.automation.jsscripting.internal;
import static org.openhab.core.automation.module.script.ScriptEngineFactory.*;
import static org.openhab.core.automation.module.script.ScriptTransformationService.OPENHAB_TRANSFORMATION_SCRIPT;
import java.io.IOException;
import java.io.InputStream;
@@ -137,24 +138,22 @@ public class OpenhabGraalJSScriptEngine
/** {@link Lock} synchronization of multi-thread access */
private final Lock lock = new ReentrantLock();
private final JSRuntimeFeatures jsRuntimeFeatures;
private final GraalJSScriptEngineConfiguration configuration;
// these fields start as null because they are populated on first use
private @Nullable Consumer<String> scriptDependencyListener;
private String engineIdentifier; // this field is very helpful for debugging, please do not remove it
private String engineIdentifier = "<uninitialized>";
private boolean initialized = false;
private final boolean injectionEnabled;
private final boolean injectionCachingEnabled;
/**
* Creates an implementation of ScriptEngine {@code (& Invocable)}, wrapping the contained engine,
* that tracks the script lifecycle and provides hooks for scripts to do so too.
*/
public OpenhabGraalJSScriptEngine(boolean injectionEnabled, boolean injectionCachingEnabled,
public OpenhabGraalJSScriptEngine(GraalJSScriptEngineConfiguration configuration,
JSScriptServiceUtil jsScriptServiceUtil, JSDependencyTracker jsDependencyTracker) {
super(null); // delegate depends on fields not yet initialised, so we cannot set it immediately
this.injectionEnabled = injectionEnabled;
this.injectionCachingEnabled = injectionCachingEnabled;
this.configuration = configuration;
this.jsRuntimeFeatures = jsScriptServiceUtil.getJSRuntimeFeatures(lock);
delegate = GraalJSScriptEngine.create(ENGINE, Context.newBuilder("js") //
@@ -163,9 +162,12 @@ public class OpenhabGraalJSScriptEngine
@Override
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException {
Consumer<String> localScriptDependencyListener = scriptDependencyListener;
if (localScriptDependencyListener != null) {
localScriptDependencyListener.accept(path.toString());
if (configuration.isDependencyTrackingEnabled()
&& path.startsWith(GraalJSScriptEngineFactory.JS_LIB_PATH)) {
Consumer<String> localScriptDependencyListener = scriptDependencyListener;
if (localScriptDependencyListener != null) {
localScriptDependencyListener.accept(path.toString());
}
}
if (path.toString().endsWith(".js")) {
@@ -244,10 +246,10 @@ public class OpenhabGraalJSScriptEngine
protected void beforeInvocation() {
super.beforeInvocation();
logger.debug("Initializing GraalJS script engine...");
logger.debug("Initializing GraalJS script engine '{}' ...", engineIdentifier);
lock.lock();
logger.debug("Lock acquired before invocation.");
logger.debug("Lock acquired before invocation for engine '{}'.", engineIdentifier);
if (initialized) {
return;
@@ -275,7 +277,8 @@ public class OpenhabGraalJSScriptEngine
.getAttribute(CONTEXT_KEY_DEPENDENCY_LISTENER);
if (localScriptDependencyListener == null) {
logger.warn(
"Failed to retrieve script script dependency listener from engine bindings. Script dependency tracking will be disabled.");
"Failed to retrieve script dependency listener from engine bindings. Script dependency tracking will be disabled for engine '{}'.",
engineIdentifier);
}
scriptDependencyListener = localScriptDependencyListener;
@@ -291,34 +294,47 @@ public class OpenhabGraalJSScriptEngine
// Injections into the JS runtime
jsRuntimeFeatures.getFeatures().forEach((key, obj) -> {
logger.debug("Injecting {} into the JS runtime...", key);
logger.debug("Injecting {} into the context of engine '{}' ...", key, engineIdentifier);
delegate.put(key, obj);
});
initialized = true;
try {
logger.debug("Evaluating cached global script...");
logger.debug("Evaluating cached global script for engine '{}' ...", engineIdentifier);
delegate.getPolyglotContext().eval(GLOBAL_SOURCE);
if (this.injectionEnabled) {
if (this.injectionCachingEnabled) {
logger.debug("Evaluating cached openhab-js injection...");
if (configuration.isInjection(GraalJSScriptEngineConfiguration.INJECTION_ENABLED_FOR_ALL_SCRIPTS)
|| (isUiBasedScript() && configuration.isInjection(
GraalJSScriptEngineConfiguration.INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY))) {
if (configuration.isInjectionCachingEnabled()) {
logger.debug("Evaluating cached openhab-js injection for engine '{}' ...", engineIdentifier);
delegate.getPolyglotContext().eval(OPENHAB_JS_SOURCE);
} else {
logger.debug("Evaluating openhab-js injection from the file system...");
logger.debug("Evaluating openhab-js injection from the file system for engine '{}' ...",
engineIdentifier);
eval(OPENHAB_JS_INJECTION_CODE);
}
}
logger.debug("Successfully initialized GraalJS script engine.");
logger.debug("Successfully initialized GraalJS script engine '{}'.", engineIdentifier);
} catch (ScriptException e) {
logger.error("Could not inject global script", e);
}
}
@Override
protected String onScript(String script) {
if (isUiBasedScript() && configuration.isWrapperEnabled()) {
logger.debug("Wrapping script for engine '{}' ...", engineIdentifier);
return "(function() {" + script + "})()";
}
return super.onScript(script);
}
@Override
protected Object afterInvocation(Object obj) {
lock.unlock();
logger.debug("Lock released after invocation.");
logger.debug("Lock released after invocation for engine '{}'.", engineIdentifier);
return super.afterInvocation(obj);
}
@@ -333,6 +349,21 @@ public class OpenhabGraalJSScriptEngine
jsRuntimeFeatures.close();
}
/**
* Tests if the current script is a UI-based script, i.e. it is neither loaded from a file nor a transformation.
*
* @return true if the script is UI-based, false otherwise
*/
private boolean isUiBasedScript() {
ScriptContext ctx = delegate.getContext();
if (ctx == null) {
logger.warn("Failed to retrieve script context from engine '{}'.", engineIdentifier);
return false;
}
return ctx.getAttribute("javax.script.filename") == null
&& !engineIdentifier.startsWith(OPENHAB_TRANSFORMATION_SCRIPT);
}
/**
* Tests if this is a root node directory, `/node_modules`, `C:\node_modules`, etc...
*
@@ -371,7 +402,7 @@ public class OpenhabGraalJSScriptEngine
@Override
public void lock() {
lock.lock();
logger.debug("Lock acquired.");
logger.debug("Lock acquired for engine '{}'.", engineIdentifier);
}
@Override
@@ -392,7 +423,7 @@ public class OpenhabGraalJSScriptEngine
@Override
public void unlock() {
lock.unlock();
logger.debug("Lock released.");
logger.debug("Lock released for engine '{}'.", engineIdentifier);
}
@Override
@@ -33,7 +33,7 @@ public abstract class DelegatingScriptEngineWithInvocableAndCompilableAndAutoclo
implements ScriptEngine, Invocable, Compilable, AutoCloseable {
protected T delegate;
public DelegatingScriptEngineWithInvocableAndCompilableAndAutocloseable(T delegate) {
protected DelegatingScriptEngineWithInvocableAndCompilableAndAutocloseable(T delegate) {
this.delegate = delegate;
}
@@ -33,26 +33,51 @@ import javax.script.ScriptException;
public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable<T extends ScriptEngine & Invocable & Compilable & AutoCloseable>
extends DelegatingScriptEngineWithInvocableAndCompilableAndAutocloseable<T> {
public InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable(T delegate) {
protected InvocationInterceptingScriptEngineWithInvocableAndCompilableAndAutoCloseable(T delegate) {
super(delegate);
}
/**
* Hook method to be called before the invocation of any method on the script engine.
*/
protected void beforeInvocation() {
}
/**
* Hook method to be called when a string script is about to be evaluated.
*
* @param script the script to be evaluated
* @return the modified script to be evaluated instead, or the original script
*/
protected String onScript(String script) {
return script;
}
/**
* Hook method to be called after the invocation of any method on the script engine.
*
* @param obj the result of the invocation
* @return the result to be returned instead, or the original result
*/
protected Object afterInvocation(Object obj) {
return obj;
}
/**
* Hook method to be called after a {@link ScriptException} or other exception is thrown during invocation.
*
* @param e the exception that was thrown
* @return the exception to be thrown instead, or the original exception
*/
protected Exception afterThrowsInvocation(Exception e) {
return e;
}
@Override
public Object eval(String s, ScriptContext scriptContext) throws ScriptException {
public Object eval(String script, ScriptContext scriptContext) throws ScriptException {
try {
beforeInvocation();
return afterInvocation(super.eval(s, scriptContext));
return afterInvocation(super.eval(onScript(script), scriptContext));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@@ -73,10 +98,10 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object eval(String s) throws ScriptException {
public Object eval(String script) throws ScriptException {
try {
beforeInvocation();
return afterInvocation(super.eval(s));
return afterInvocation(super.eval(onScript(script)));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@@ -97,10 +122,10 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object eval(String s, Bindings bindings) throws ScriptException {
public Object eval(String script, Bindings bindings) throws ScriptException {
try {
beforeInvocation();
return afterInvocation(super.eval(s, bindings));
return afterInvocation(super.eval(onScript(script), bindings));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@@ -121,11 +146,11 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object invokeMethod(Object o, String s, Object... objects)
public Object invokeMethod(Object thiz, String name, Object... args)
throws ScriptException, NoSuchMethodException, NullPointerException, IllegalArgumentException {
try {
beforeInvocation();
return afterInvocation(super.invokeMethod(o, s, objects));
return afterInvocation(super.invokeMethod(thiz, name, args));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (NoSuchMethodException e) { // Make sure to unlock on exceptions from Invocable.invokeMethod to avoid
@@ -141,11 +166,11 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public Object invokeFunction(String s, Object... objects)
public Object invokeFunction(String name, Object... args)
throws ScriptException, NoSuchMethodException, NullPointerException {
try {
beforeInvocation();
return afterInvocation(super.invokeFunction(s, objects));
return afterInvocation(super.invokeFunction(name, args));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (NoSuchMethodException e) { // Make sure to unlock on exceptions from Invocable.invokeFunction to avoid
@@ -159,10 +184,10 @@ public abstract class InvocationInterceptingScriptEngineWithInvocableAndCompilab
}
@Override
public CompiledScript compile(String s) throws ScriptException {
public CompiledScript compile(String script) throws ScriptException {
try {
beforeInvocation();
return (CompiledScript) afterInvocation(super.compile(s));
return (CompiledScript) afterInvocation(super.compile(onScript(script)));
} catch (ScriptException se) {
throw (ScriptException) afterThrowsInvocation(se);
} catch (Exception e) {
@@ -5,29 +5,56 @@
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="automation:jsscripting">
<parameter name="injectionEnabled" type="boolean" required="true">
<label>Use Built-in Global Variables</label>
<parameter-group name="environment">
<label>JavaScript Environment</label>
<description>This group defines JavaScript's environment.</description>
</parameter-group>
<parameter-group name="system">
<label>System Behaviour</label>
<description>This group defines JavaScript's system behaviour.</description>
</parameter-group>
<parameter name="injectionEnabledV2" type="integer" required="true" min="0" max="2" groupName="environment">
<label>Inject Global Variables from Helper Library</label>
<description><![CDATA[
Import all variables from the openHAB JavaScript library into all rules for common services like items, things, actions, log, etc... <br>
If disabled, the openHAB JavaScript library can be imported manually using "<i>require('openhab')</i>"
Import all variables from the openHAB JavaScript library for common services like items, things, actions, log, etc... <br>
If disabled, the openHAB JavaScript library can be imported manually using"<code>require('openhab')</code>.
]]></description>
<options>
<option value="true">Use Built-in Variables</option>
<option value="false">Do Not Use Built-in Variables</option>
<option value="2">Auto injection enabled only for UI-based scripts (recommended)</option>
<option value="1">Auto injection enabled for all scripts, i.e. including file-based scripts</option>
<option value="0">Disable auto-injection and import manually instead</option>
</options>
<default>true</default>
<default>2</default>
</parameter>
<parameter name="injectionCachingEnabled" type="boolean" required="true">
<parameter name="wrapperEnabled" type="boolean" required="true" groupName="environment">
<label>Wrap UI-based scripts in Self-Executing Function</label>
<description><![CDATA[
Wrapping UI-based scripts in a self-executing function allows the use of the <code>let</code> and <code>const</code> variable declarations,
as well as the use of <code>function</code> and <code>class</code> declarations.<br>
With this option enabled, you can also use <code>return</code> statements in your scripts to abort execution at any point.
]]></description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="injectionCachingEnabled" type="boolean" required="true" groupName="system">
<label>Cache openHAB JavaScript Library Injection</label>
<description><![CDATA[
Cache the openHAB JavaScript library injection for optimal performance.<br>
Disable this option to allow loading the library from the local user configuration directory "automation/js/node_modules". Disabling caching may increase script loading times, especially on less powerful systems.
]]></description>
<options>
<option value="true">Cache Library Injection</option>
<option value="false">Do Not Cache Library Injection</option>
</options>
<default>true</default>
</parameter>
<parameter name="dependencyTrackingEnabled" type="boolean" required="true" groupName="system">
<label>Enable Dependency Tracking</label>
<description>Dependency tracking allows your scripts to automatically reload when one of its dependencies is updated.
You may want to disable dependency tracking if you plan on editing or updating a shared library, but don't want all
your scripts to reload until you can test it. Please note that changing this setting only applies to scripts loaded
after the change.</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>
@@ -5,11 +5,18 @@ addon.jsscripting.description = This adds a JS (ECMAScript-2024) script engine.
# add-on config
automation.config.jsscripting.dependencyTrackingEnabled.label = Enable Dependency Tracking
automation.config.jsscripting.dependencyTrackingEnabled.description = Dependency tracking allows your scripts to automatically reload when one of its dependencies is updated. You may want to disable dependency tracking if you plan on editing or updating a shared library, but don't want all your scripts to reload until you can test it. Please note that changing this setting only applies to scripts loaded after the change.
automation.config.jsscripting.group.environment.label = JavaScript Environment
automation.config.jsscripting.group.environment.description = This group defines JavaScript's environment.
automation.config.jsscripting.group.system.label = System Behaviour
automation.config.jsscripting.group.system.description = This group defines JavaScript's system behaviour.
automation.config.jsscripting.injectionCachingEnabled.label = Cache openHAB JavaScript Library Injection
automation.config.jsscripting.injectionCachingEnabled.description = Cache the openHAB JavaScript library injection for optimal performance.<br> Disable this option to allow loading the library from the local user configuration directory "automation/js/node_modules". Disabling caching may increase script loading times, especially on less powerful systems.
automation.config.jsscripting.injectionCachingEnabled.option.true = Cache Library Injection
automation.config.jsscripting.injectionCachingEnabled.option.false = Do Not Cache Library Injection
automation.config.jsscripting.injectionEnabled.label = Use Built-in Global Variables
automation.config.jsscripting.injectionEnabled.description = Import all variables from the openHAB JavaScript library into all rules for common services like items, things, actions, log, etc... <br> If disabled, the openHAB JavaScript library can be imported manually using "<i>require('openhab')</i>"
automation.config.jsscripting.injectionEnabled.option.true = Use Built-in Variables
automation.config.jsscripting.injectionEnabled.option.false = Do Not Use Built-in Variables
automation.config.jsscripting.injectionEnabledV2.label = Inject Global Variables from Helper Library
automation.config.jsscripting.injectionEnabledV2.description = Import all variables from the openHAB JavaScript library for common services like items, things, actions, log, etc... <br> If disabled, the openHAB JavaScript library can be imported manually using"<code>require('openhab')</code>.
automation.config.jsscripting.injectionEnabledV2.option.2 = Auto injection enabled only for UI-based scripts (recommended)
automation.config.jsscripting.injectionEnabledV2.option.1 = Auto injection enabled for all scripts, i.e. including file-based scripts
automation.config.jsscripting.injectionEnabledV2.option.0 = Disable auto-injection and import manually instead
automation.config.jsscripting.wrapperEnabled.label = Wrap UI-based scripts in Self-Executing Function
automation.config.jsscripting.wrapperEnabled.description = Wrapping UI-based scripts in a self-executing function allows the use of the <code>let</code> and <code>const</code> variable declarations, as well as the use of <code>function</code> and <code>class</code> declarations.<br> With this option enabled, you can also use <code>return</code> statements in your scripts to abort execution at any point.