Browse Source

Merge pull request #15667 from thingsboard/master-rc-merge

Merge rc into master
pull/15544/merge
Viacheslav Klimov 2 weeks ago
committed by GitHub
parent
commit
4386bbf2d7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      TEST_FAST.md
  2. 17
      application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java
  3. 10
      common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java
  4. 12
      common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java
  5. 14
      common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java
  6. 3
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java
  7. 10
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  8. 73
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java
  9. 1
      msa/black-box-tests/src/test/resources/connectivity.xml
  10. 25
      msa/js-executor/api/jsExecutor.ts
  11. 8
      msa/js-executor/config/default.yml
  12. 2
      msa/js-executor/package.json
  13. 11
      msa/js-executor/pom.xml
  14. 83
      msa/js-executor/test/jsExecutor.test.ts
  15. 2
      msa/js-executor/tsconfig.json
  16. 18
      pom.xml
  17. 13
      ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts
  18. 32
      ui-ngx/yarn.lock

1
TEST_FAST.md

@ -10,6 +10,7 @@ export SUREFIRE_JAVA_OPTS="-Xmx1200m -Xss256k -XX:+ExitOnOutOfMemoryError"
mvn clean install -T6 -DskipTests -Dpkg.skip=true mvn clean install -T6 -DskipTests -Dpkg.skip=true
mvn test -pl='!application,!dao,!ui-ngx,!msa/js-executor,!msa/web-ui' -T4 mvn test -pl='!application,!dao,!ui-ngx,!msa/js-executor,!msa/web-ui' -T4
mvn test -pl='msa/js-executor'
mvn test -pl dao -Dparallel=packages -DforkCount=4 mvn test -pl dao -Dparallel=packages -DforkCount=4
mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5

17
application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java

@ -644,4 +644,21 @@ public class RuleChainControllerTest extends AbstractControllerTest {
return doPost("/api/ruleChain", ruleChain, RuleChain.class); return doPost("/api/ruleChain", ruleChain, RuleChain.class);
} }
@Test
public void testScriptForbiddenForCustomer() throws Exception {
loginCustomerUser();
doPost("/api/ruleChain/testScript", (Object) """
{
"script": "return msg;",
"scriptType": "update",
"argNames": ["msg", "metadata", "msgType"],
"msg": "{}",
"metadata": {},
"msgType": "POST_TELEMETRY_REQUEST"
}
""")
.andExpect(status().isForbidden());
}
} }

10
common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java

@ -25,12 +25,10 @@ import java.util.List;
public record TbUserMessage( public record TbUserMessage(
@NotEmpty @NotEmpty
@Valid @Valid
@ArraySchema( @Schema(
arraySchema = @Schema( requiredMode = Schema.RequiredMode.REQUIRED,
requiredMode = Schema.RequiredMode.REQUIRED, description = "A list of content parts that make up the complete user prompt"
description = "A list of content parts that make up the complete user prompt"
),
schema = @Schema(ref = "#/components/schemas/TbContent")
) )
@ArraySchema(schema = @Schema(ref = "#/components/schemas/TbContent"))
List<TbContent> contents List<TbContent> contents
) {} ) {}

12
common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java

@ -37,17 +37,21 @@ public record AvailableEntityKeys(
) )
Set<EntityType> entityTypes, Set<EntityType> entityTypes,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) @Schema(
requiredMode = Schema.RequiredMode.REQUIRED,
description = "List of unique time series key names available on the matched entities."
)
@ArraySchema( @ArraySchema(
arraySchema = @Schema(description = "List of unique time series key names available on the matched entities."),
schema = @Schema(implementation = String.class, example = "temperature"), schema = @Schema(implementation = String.class, example = "temperature"),
uniqueItems = true uniqueItems = true
) )
List<String> timeseries, List<String> timeseries,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) @Schema(
requiredMode = Schema.RequiredMode.REQUIRED,
description = "List of unique attribute key names available on the matched entities."
)
@ArraySchema( @ArraySchema(
arraySchema = @Schema(description = "List of unique attribute key names available on the matched entities."),
schema = @Schema(implementation = String.class, example = "serialNumber"), schema = @Schema(implementation = String.class, example = "serialNumber"),
uniqueItems = true uniqueItems = true
) )

14
common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java

@ -41,15 +41,13 @@ public record AvailableEntityKeysV2(
) )
Set<EntityType> entityTypes, Set<EntityType> entityTypes,
@ArraySchema( @Schema(
arraySchema = @Schema( description = """
description = """ List of unique time series keys available on the matched entities, sorted alphabetically.
List of unique time series keys available on the matched entities, sorted alphabetically. Omitted when timeseries keys were not requested.""",
Omitted when timeseries keys were not requested.""", nullable = true
nullable = true
),
schema = @Schema(implementation = KeyInfo.class)
) )
@ArraySchema(schema = @Schema(implementation = KeyInfo.class))
@Nullable List<KeyInfo> timeseries, @Nullable List<KeyInfo> timeseries,
@Schema( @Schema(

3
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java

@ -36,7 +36,8 @@ public class WidgetsBundleExportData extends EntityExportData<WidgetsBundle> {
@Override @Override
public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; } public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; }
@ArraySchema(arraySchema = @Schema(description = "List of widgets in the bundle"), schema = @Schema(implementation = JsonNode.class)) @Schema(description = "List of widgets in the bundle")
@ArraySchema(schema = @Schema(implementation = JsonNode.class))
@JsonProperty(index = 3) @JsonProperty(index = 3)
private List<ObjectNode> widgets; private List<ObjectNode> widgets;

10
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -358,6 +358,16 @@ public class TestRestClient {
.statusCode(HTTP_OK); .statusCode(HTTP_OK);
} }
public JsonNode testRuleChainScript(Object body) {
return given().spec(requestSpec)
.body(body)
.post("/api/ruleChain/testScript")
.then()
.statusCode(HTTP_OK)
.extract()
.as(JsonNode.class);
}
private String getUrlParams(PageLink pageLink) { private String getUrlParams(PageLink pageLink) {
String urlParams = "pageSize={pageSize}&page={page}"; String urlParams = "pageSize={pageSize}&page={page}";
if (!isEmpty(pageLink.getTextSearch())) { if (!isEmpty(pageLink.getTextSearch())) {

73
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java

@ -0,0 +1,73 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.security;
import com.fasterxml.jackson.databind.JsonNode;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.AbstractContainerTest;
import static org.assertj.core.api.Assertions.assertThat;
public class JsExecutorSandboxIsolationTest extends AbstractContainerTest {
@BeforeClass
public void beforeClass() {
testRestClient.login("tenant@thingsboard.org", "tenant");
}
@AfterClass
public void afterClass() {
testRestClient.resetToken();
}
/**
* Black-box regression for JVN#16937365: a tenant admin must not be able
* to escape the tb-js-executor sandbox via the host-realm prototype chain
* exposed through the script's `args` argument. Runs against the live
* docker-compose deployment, which uses script.use_sandbox=true and
* JS_EVALUATOR=remote (Kafka -> tb-js-executor).
*/
@Test
public void testRuleChainScriptCannotReachHostProcess() {
JsonNode response = testRestClient.testRuleChainScript("""
{
"script": "var F = args.constructor.constructor; var p = F('return process')(); return { reachedHost: !!(p && p.mainModule) };",
"scriptType": "update",
"argNames": ["msg", "metadata", "msgType"],
"msg": "{}",
"metadata": {},
"msgType": "POST_TELEMETRY_REQUEST"
}
""");
// The sandboxed run must reject the escape attempt: the host `process`
// global is not defined inside the sandbox realm, so executing the
// synthesized function `F("return process")` throws.
assertThat(response.has("error")).isTrue();
String error = response.get("error").asText();
assertThat(error)
.as("sandbox must block host-realm reach via args.constructor.constructor; full error: %s", error)
.contains("process is not defined");
// Defense in depth: even if the script somehow returned, output must
// not indicate that the host process was reached.
if (response.hasNonNull("output")) {
assertThat(response.get("output").asText()).doesNotContain("\"reachedHost\":true");
}
}
}

1
msa/black-box-tests/src/test/resources/connectivity.xml

@ -25,6 +25,7 @@
<package name="org.thingsboard.server.msa.edqs"/> <package name="org.thingsboard.server.msa.edqs"/>
<package name="org.thingsboard.server.msa.cf"/> <package name="org.thingsboard.server.msa.cf"/>
<package name="org.thingsboard.server.msa.rule.node"/> <package name="org.thingsboard.server.msa.rule.node"/>
<package name="org.thingsboard.server.msa.security"/>
</packages> </packages>
</test> </test>
</suite> </suite>

25
msa/js-executor/api/jsExecutor.ts

@ -15,14 +15,22 @@
/// ///
import vm, { Script } from 'vm'; import vm, { Script } from 'vm';
import { _logger } from '../config/logger';
export type TbScript = Script | Function; export type TbScript = Script | Function;
export class JsExecutor { export class JsExecutor {
useSandbox: boolean; useSandbox: boolean;
private logger = _logger('JsExecutor');
constructor(useSandbox: boolean) { constructor(useSandbox: boolean) {
this.useSandbox = useSandbox; this.useSandbox = useSandbox;
if (!useSandbox) {
this.logger.warn(
'script.use_sandbox=false: dangerous by design — user-supplied scripts run in the host realm with no isolation. ' +
'Use only as a performance trade-off in trusted, non-public clusters.'
);
}
} }
compileScript(code: string): Promise<TbScript> { compileScript(code: string): Promise<TbScript> {
@ -56,9 +64,15 @@ export class JsExecutor {
private invokeScript(script: Script, args: string[], timeout: number | undefined): Promise<any> { private invokeScript(script: Script, args: string[], timeout: number | undefined): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const sandbox = Object.create(null); const sandbox = vm.createContext(Object.create(null));
sandbox.args = args; // Construct args inside the sandbox context so it inherits sandbox-realm
const result = script.runInNewContext(sandbox, {timeout: timeout}); // prototypes; prevents prototype-based escapes from the host realm.
const ctxArgs = vm.runInContext('[]', sandbox) as string[];
for (let i = 0; i < args.length; i++) {
ctxArgs[i] = String(args[i]);
}
sandbox.args = ctxArgs;
const result = script.runInContext(sandbox, {timeout: timeout});
resolve(result); resolve(result);
} catch (err) { } catch (err) {
reject(err); reject(err);
@ -67,6 +81,11 @@ export class JsExecutor {
} }
// DANGEROUS BY DESIGN: the non-sandbox path. vm.compileFunction's
// parsingContext only isolates *parsing*, not *execution* — the resulting
// function runs in the host realm with full access to host globals
// (process, require, etc.). Enabled only via script.use_sandbox=false as
// a performance trade-off in trusted clusters.
private createFunction(code: string): Promise<Function> { private createFunction(code: string): Promise<Function> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {

8
msa/js-executor/config/default.yml

@ -60,7 +60,13 @@ logger:
# JavaScript execution and monitoring configuration # JavaScript execution and monitoring configuration
# Sandboxing, script caching, and observability settings for the JS script runtime. # Sandboxing, script caching, and observability settings for the JS script runtime.
script: script:
use_sandbox: "true" # Run scripts inside an isolated sandbox to prevent access to Node.js internals # WARNING: setting this to "false" is DANGEROUS BY DESIGN. The non-sandbox
# path compiles and runs user-supplied scripts in the host realm via
# vm.compileFunction; it provides no isolation and exposes the host process
# (file system, environment variables, child_process, etc.) to script
# authors. Use "false" only as a performance trade-off in trusted,
# non-public clusters where every script author is fully trusted.
use_sandbox: "true"
memory_usage_trace_frequency: "1000" # Log memory usage every N script executions memory_usage_trace_frequency: "1000" # Log memory usage every N script executions
script_body_trace_frequency: "10000" # Log the script body every N script executions script_body_trace_frequency: "10000" # Log the script body every N script executions
stat_print_frequency: "10000" # Print execution statistics every N script executions stat_print_frequency: "10000" # Print execution statistics every N script executions

2
msa/js-executor/package.json

@ -7,7 +7,7 @@
"bin": "server.js", "bin": "server.js",
"scripts": { "scripts": {
"pkg": "tsc && pkg -t node22-linux-x64 --output ./target/thingsboard-js-executor-linux ./target/src && pkg -t node22-win-x64 --no-bytecode --public-packages \"*\" --public --output ./target/thingsboard-js-executor-win.exe ./target/src && node install.js", "pkg": "tsc && pkg -t node22-linux-x64 --output ./target/thingsboard-js-executor-linux ./target/src && pkg -t node22-win-x64 --no-bytecode --public-packages \"*\" --public --output ./target/thingsboard-js-executor-win.exe ./target/src && node install.js",
"test": "echo \"Error: no test specified\" && exit 1", "test": "mkdir -p target/surefire-reports && node --require ts-node/register --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=target/surefire-reports/TEST-js-executor.xml test/jsExecutor.test.ts",
"start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'", "start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'", "start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'",
"build": "tsc" "build": "tsc"

11
msa/js-executor/pom.xml

@ -116,6 +116,17 @@
<arguments>--mutex network run pkg</arguments> <arguments>--mutex network run pkg</arguments>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>yarn test</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>test</phase>
<configuration>
<skip>${maven.test.skip}</skip>
<arguments>--mutex network run test</arguments>
</configuration>
</execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>

83
msa/js-executor/test/jsExecutor.test.ts

@ -0,0 +1,83 @@
///
/// Copyright © 2016-2026 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { JsExecutor } from '../api/jsExecutor';
// describe('js-executor') groups all cases under <testsuite name="js-executor">
// in the JUnit XML so they show up under that suite in TeamCity's Tests tab,
// alongside thousands of Java tests.
describe('js-executor', () => {
test('sandbox isolates args from host realm (JVN#16937365)', async () => {
const exec = new JsExecutor(true);
const script = await exec.compileScript(`function(msg, metadata, msgType){
var F = args.constructor.constructor;
var p = F("return process")();
return p && p.mainModule ? 'reached-host' : 'isolated';
}`);
await assert.rejects(
exec.executeScript(script, ['{}', '{}', 'POST_TELEMETRY_REQUEST'], 5000),
/process is not defined/,
'host process must not be reachable from inside the sandbox',
);
});
test('sandbox passes string args through unchanged', async () => {
const exec = new JsExecutor(true);
const script = await exec.compileScript(`function(msg, metadata, msgType){
return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] };
}`);
const out = await exec.executeScript(script, ['hello', '{}', 'X'], 5000);
// Field-by-field: the returned object is owned by the sandbox realm, so
// its prototype is not the host Object.prototype and deepStrictEqual would
// reject it on prototype mismatch even when the values match.
assert.equal(out.msgIsString, true);
assert.equal(out.count, 3);
assert.equal(out.first, 'hello');
});
// The use_sandbox=false path is intentionally non-isolating: scripts compile
// and run in the host realm via vm.compileFunction. The two tests below codify
// that documented contract so any future behavior change shows up as a test
// failure and forces a deliberate update of the docs and threat model.
test('non-sandbox path does not isolate from host realm (documented contract)', async () => {
const exec = new JsExecutor(false);
const script = await exec.compileScript(`function(msg, metadata, msgType){
// Non-destructive host-reach probe: typeof process.platform is 'string'
// only if the host process object is reachable.
var F = args.constructor.constructor;
return F('return typeof process.platform')();
}`);
const out = await exec.executeScript(script, ['{}', '{}', 'X']);
assert.equal(out, 'string',
'use_sandbox=false is documented as non-isolating; if this fails, the path was changed and docs/threat model must be updated');
});
test('non-sandbox path passes string args through unchanged', async () => {
const exec = new JsExecutor(false);
const script = await exec.compileScript(`function(msg, metadata, msgType){
return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] };
}`);
const out = await exec.executeScript(script, ['hello', '{}', 'X']);
assert.equal(out.msgIsString, true);
assert.equal(out.count, 3);
assert.equal(out.first, 'hello');
});
}); // describe('js-executor')

2
msa/js-executor/tsconfig.json

@ -9,5 +9,5 @@
"skipLibCheck": true, "skipLibCheck": true,
"strictPropertyInitialization": false "strictPropertyInitialization": false
}, },
"exclude": ["node_modules", "target"] "exclude": ["node_modules", "target", "test"]
} }

18
pom.xml

@ -71,6 +71,7 @@
<commons-lang3.version>3.18.0</commons-lang3.version> <!-- to fix CVE-2025-48924. TODO: remove when fixed in spring-boot-dependencies --> <commons-lang3.version>3.18.0</commons-lang3.version> <!-- to fix CVE-2025-48924. TODO: remove when fixed in spring-boot-dependencies -->
<postgresql.version>42.7.11</postgresql.version> <!-- to fix CVE-2026-42198. TODO: remove when fixed in spring-boot-dependencies --> <postgresql.version>42.7.11</postgresql.version> <!-- to fix CVE-2026-42198. TODO: remove when fixed in spring-boot-dependencies -->
<netty.version>4.1.133.Final</netty.version> <!-- to fix CVE-2026-42579, CVE-2026-42583, CVE-2026-42584, CVE-2026-42587. TODO: remove when fixed in spring-boot-dependencies --> <netty.version>4.1.133.Final</netty.version> <!-- to fix CVE-2026-42579, CVE-2026-42583, CVE-2026-42584, CVE-2026-42587. TODO: remove when fixed in spring-boot-dependencies -->
<tomcat.version>10.1.55</tomcat.version> <!-- to fix CVE-2026-41284, CVE-2026-43512. TODO: remove when fixed in spring-boot-dependencies -->
<javax.xml.bind-api.version>2.4.0-b180830.0359</javax.xml.bind-api.version> <javax.xml.bind-api.version>2.4.0-b180830.0359</javax.xml.bind-api.version>
<jjwt.version>0.12.5</jjwt.version> <jjwt.version>0.12.5</jjwt.version>
<rat.version>0.10</rat.version> <!-- unused --> <rat.version>0.10</rat.version> <!-- unused -->
@ -1019,6 +1020,23 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- End of netty-bom version override --> <!-- End of netty-bom version override -->
<!-- Temporary tomcat-embed version override -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- End of tomcat-embed version override -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId> <artifactId>spring-boot-dependencies</artifactId>

13
ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts

@ -24,7 +24,7 @@ import {
ViewChild, ViewChild,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ImageService } from '@app/core/public-api'; import { ImageService } from '@app/core/public-api';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { AttributeService } from '@core/http/attribute.service'; import { AttributeService } from '@core/http/attribute.service';
@ -63,8 +63,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
protected store: Store<AppState>, protected store: Store<AppState>,
private imageService: ImageService, private imageService: ImageService,
private utils: UtilsService, private utils: UtilsService,
private attributeService: AttributeService, private attributeService: AttributeService
private sanitizer: DomSanitizer
) { ) {
super(store); super(store);
} }
@ -115,8 +114,8 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
isLoading = false; isLoading = false;
singleDevice = true; singleDevice = true;
updatePhoto = false; updatePhoto = false;
previewPhoto: SafeUrl; previewPhoto: string;
lastPhoto: SafeUrl; lastPhoto: string;
datasourceDetected = false; datasourceDetected = false;
private mimeType: string; private mimeType: string;
@ -176,7 +175,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
private updateWidgetData(data: Array<DatasourceData>) { private updateWidgetData(data: Array<DatasourceData>) {
const keyData = data[0].data; const keyData = data[0].data;
if (keyData?.length && isString(keyData[0][1])) { if (keyData?.length && isString(keyData[0][1])) {
this.lastPhoto = keyData[0][1].startsWith('data:image/') ? this.sanitizer.bypassSecurityTrustUrl(keyData[0][1]) : keyData[0][1]; this.lastPhoto = keyData[0][1];
} }
} }
@ -309,7 +308,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
const file = new File([blob], fileName, { type: this.mimeType }); const file = new File([blob], fileName, { type: this.mimeType });
return this.imageService.uploadImage(file, fileName); return this.imageService.uploadImage(file, fileName);
}), }),
map((imageInfo) => map((imageInfo) =>
this.settings.usePublicGalleryLink ? imageInfo.publicLink : imageInfo.link this.settings.usePublicGalleryLink ? imageInfo.publicLink : imageInfo.link
) )
); );

32
ui-ngx/yarn.lock

@ -987,14 +987,14 @@
"@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-plugin-utils" "^7.28.6"
"@babel/plugin-transform-modules-systemjs@^7.27.1": "@babel/plugin-transform-modules-systemjs@^7.27.1":
version "7.28.5" version "7.29.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz#f621105da99919c15cf4bde6fcc7346ef95e7b20"
integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== integrity sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==
dependencies: dependencies:
"@babel/helper-module-transforms" "^7.28.3" "@babel/helper-module-transforms" "^7.28.6"
"@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-plugin-utils" "^7.28.6"
"@babel/helper-validator-identifier" "^7.28.5" "@babel/helper-validator-identifier" "^7.28.5"
"@babel/traverse" "^7.28.5" "@babel/traverse" "^7.29.0"
"@babel/plugin-transform-modules-umd@^7.27.1": "@babel/plugin-transform-modules-umd@^7.27.1":
version "7.27.1" version "7.27.1"
@ -1298,7 +1298,7 @@
"@babel/parser" "^7.28.6" "@babel/parser" "^7.28.6"
"@babel/types" "^7.28.6" "@babel/types" "^7.28.6"
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6": "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0":
version "7.29.0" version "7.29.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a"
integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==
@ -4349,17 +4349,17 @@ boolbase@^1.0.0:
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.13" version "1.1.14"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b"
integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==
dependencies: dependencies:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
brace-expansion@^5.0.2: brace-expansion@^5.0.2:
version "5.0.5" version "5.0.6"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285"
integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==
dependencies: dependencies:
balanced-match "^4.0.2" balanced-match "^4.0.2"
@ -6113,9 +6113,9 @@ fast-levenshtein@^2.0.6:
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-uri@^3.0.1: fast-uri@^3.0.1:
version "3.1.0" version "3.1.2"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec"
integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==
fastq@^1.6.0: fastq@^1.6.0:
version "1.17.1" version "1.17.1"

Loading…
Cancel
Save