diff --git a/api/src/client/package-lock.json b/api/src/client/package-lock.json
index a04334e0ba..904cda5474 100644
--- a/api/src/client/package-lock.json
+++ b/api/src/client/package-lock.json
@@ -17,12 +17,13 @@
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"chai": "^4.3.7",
"compression-webpack-plugin": "^10.0.0",
+ "copy-webpack-plugin": "^14.0.0",
"cross-env": "^7.0.3",
"fs-extra": "^11.1.1",
"global-jsdom": "^29.0.0",
"html-webpack-plugin": "^5.5.1",
"jsdom": "^29.1.0",
- "jsf.js_next_gen": "4.1.0-beta.14",
+ "jsf.js_next_gen": "4.1.0-beta.16",
"mocha": "^11.7.5",
"npm-check-updates": "^22.0.1",
"nyc": "^18.0.0",
@@ -2464,9 +2465,9 @@
}
},
"node_modules/body-parser": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
- "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2478,7 +2479,7 @@
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
- "qs": "~6.14.0",
+ "qs": "~6.15.1",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
@@ -3074,6 +3075,43 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
+ "node_modules/copy-webpack-plugin": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz",
+ "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob-parent": "^6.0.1",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^7.0.3",
+ "tinyglobby": "^0.2.12"
+ },
+ "engines": {
+ "node": ">= 20.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -3691,15 +3729,15 @@
}
},
"node_modules/express": {
- "version": "4.22.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
- "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "~1.20.3",
+ "body-parser": "~1.20.5",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
@@ -3718,7 +3756,7 @@
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "~6.14.0",
+ "qs": "~6.15.1",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
@@ -5052,13 +5090,13 @@
}
},
"node_modules/jsf.js_next_gen": {
- "version": "4.1.0-beta.14",
- "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.14.tgz",
- "integrity": "sha512-fdFD6+7brgddZe1ZWVEJxHstuKe4qLOenjI33ZPBqNpQJVa7ghTkt+vHgkkruV6hw68UY65Dmmu/GvTpdI7P2g==",
+ "version": "4.1.0-beta.16",
+ "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.16.tgz",
+ "integrity": "sha512-YxO3gAvPxEG6gHN0S2Yxd1YG7/ULfR7LnED11lEC8pKkALvm6Ez2Ize/P/BGL6lVEJTCx6XkFBcUHeWev3jjmg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "mona-dish": "0.40.0-beta.4"
+ "mona-dish": "0.50.0-beta.2"
}
},
"node_modules/json-schema-traverse": {
@@ -5529,9 +5567,9 @@
}
},
"node_modules/mona-dish": {
- "version": "0.40.0-beta.4",
- "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.40.0-beta.4.tgz",
- "integrity": "sha512-Cx735lpHSYtQeDNKwE+Gy7Z3AckJr6kcst74d0+TDM5w6up4yDLUFfSj4g6KaH4jkYLMEVT5vIhZK4gzV36sBw==",
+ "version": "0.50.0-beta.2",
+ "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.50.0-beta.2.tgz",
+ "integrity": "sha512-GTn/ZOrGMlemVcq+Q7FOqREs8j5IfuoESqXmi37tPKnV4GkjpXc7cQNELj9ZLWRF0BSKAQ/NrSRJqG2EPAg5yg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5705,9 +5743,9 @@
}
},
"node_modules/nyc/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6378,9 +6416,9 @@
}
},
"node_modules/qs": {
- "version": "6.14.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
- "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -6677,9 +6715,9 @@
}
},
"node_modules/rimraf/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7532,9 +7570,9 @@
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7619,6 +7657,54 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/tldts": {
"version": "7.0.29",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz",
@@ -7905,9 +7991,9 @@
}
},
"node_modules/typedoc/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8494,10 +8580,11 @@
}
},
"node_modules/ws": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
- "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -10335,9 +10422,9 @@
"dev": true
},
"body-parser": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
- "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
"dev": true,
"requires": {
"bytes": "~3.1.2",
@@ -10348,7 +10435,7 @@
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
- "qs": "~6.14.0",
+ "qs": "~6.15.1",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
@@ -10775,6 +10862,30 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
+ "copy-webpack-plugin": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz",
+ "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==",
+ "dev": true,
+ "requires": {
+ "glob-parent": "^6.0.1",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": ">=7.0.5",
+ "tinyglobby": "^0.2.12"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ }
+ }
+ },
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -11210,14 +11321,14 @@
"dev": true
},
"express": {
- "version": "4.22.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
- "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
"dev": true,
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "~1.20.3",
+ "body-parser": "~1.20.5",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
@@ -11236,7 +11347,7 @@
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "~6.14.0",
+ "qs": "~6.15.1",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
@@ -12166,12 +12277,12 @@
"dev": true
},
"jsf.js_next_gen": {
- "version": "4.1.0-beta.14",
- "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.14.tgz",
- "integrity": "sha512-fdFD6+7brgddZe1ZWVEJxHstuKe4qLOenjI33ZPBqNpQJVa7ghTkt+vHgkkruV6hw68UY65Dmmu/GvTpdI7P2g==",
+ "version": "4.1.0-beta.16",
+ "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.16.tgz",
+ "integrity": "sha512-YxO3gAvPxEG6gHN0S2Yxd1YG7/ULfR7LnED11lEC8pKkALvm6Ez2Ize/P/BGL6lVEJTCx6XkFBcUHeWev3jjmg==",
"dev": true,
"requires": {
- "mona-dish": "0.40.0-beta.4"
+ "mona-dish": "0.50.0-beta.2"
}
},
"json-schema-traverse": {
@@ -12514,9 +12625,9 @@
}
},
"mona-dish": {
- "version": "0.40.0-beta.4",
- "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.40.0-beta.4.tgz",
- "integrity": "sha512-Cx735lpHSYtQeDNKwE+Gy7Z3AckJr6kcst74d0+TDM5w6up4yDLUFfSj4g6KaH4jkYLMEVT5vIhZK4gzV36sBw==",
+ "version": "0.50.0-beta.2",
+ "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.50.0-beta.2.tgz",
+ "integrity": "sha512-GTn/ZOrGMlemVcq+Q7FOqREs8j5IfuoESqXmi37tPKnV4GkjpXc7cQNELj9ZLWRF0BSKAQ/NrSRJqG2EPAg5yg==",
"dev": true,
"requires": {
"token": "^0.1.0"
@@ -12651,9 +12762,9 @@
"dev": true
},
"brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"requires": {
"balanced-match": "^4.0.2"
@@ -13148,9 +13259,9 @@
"dev": true
},
"qs": {
- "version": "6.14.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
- "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true,
"requires": {
"side-channel": "^1.1.0"
@@ -13357,9 +13468,9 @@
"dev": true
},
"brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"requires": {
"balanced-match": "^4.0.2"
@@ -13981,9 +14092,9 @@
"dev": true
},
"brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"requires": {
"balanced-match": "^4.0.2"
@@ -14034,6 +14145,31 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
+ "tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "requires": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "dependencies": {
+ "fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "requires": {}
+ },
+ "picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true
+ }
+ }
+ },
"tldts": {
"version": "7.0.29",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz",
@@ -14222,9 +14358,9 @@
"dev": true
},
"brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"requires": {
"balanced-match": "^4.0.2"
@@ -14612,9 +14748,9 @@
}
},
"ws": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
- "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"dev": true,
"requires": {}
},
diff --git a/api/src/client/package.json b/api/src/client/package.json
index 0a9a67f536..ffa2863146 100644
--- a/api/src/client/package.json
+++ b/api/src/client/package.json
@@ -19,12 +19,13 @@
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"chai": "^4.3.7",
"compression-webpack-plugin": "^10.0.0",
+ "copy-webpack-plugin": "^14.0.0",
"cross-env": "^7.0.3",
"fs-extra": "^11.1.1",
- "html-webpack-plugin": "^5.5.1",
"global-jsdom": "^29.0.0",
+ "html-webpack-plugin": "^5.5.1",
"jsdom": "^29.1.0",
- "jsf.js_next_gen": "4.1.0-beta.14",
+ "jsf.js_next_gen": "4.1.0-beta.16",
"mocha": "^11.7.5",
"npm-check-updates": "^22.0.1",
"nyc": "^18.0.0",
diff --git a/api/src/client/typescript/faces/@types/definitions/faces.d.ts b/api/src/client/typescript/faces/@types/definitions/faces.d.ts
new file mode 100644
index 0000000000..340ea3e62f
--- /dev/null
+++ b/api/src/client/typescript/faces/@types/definitions/faces.d.ts
@@ -0,0 +1,293 @@
+/*! Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.
+ */
+
+declare namespace faces {
+ /**
+ * Project stage values, mirroring jakarta.faces.application.ProjectStage.
+ */
+ export type ProjectStage = "Development" | "UnitTest" | "SystemTest" | "Production";
+ /**
+ * Status values sent to ajax event callbacks.
+ */
+ export type AjaxEventStatus = "begin" | "complete" | "success";
+ /**
+ * Status values sent to ajax error callbacks.
+ */
+ export type AjaxErrorStatus = "httpError" | "emptyResponse" | "malformedXML" | "serverError";
+ /**
+ * Common shape for the data passed to ajax callbacks.
+ */
+ export interface AjaxData {
+ source?: Element;
+ responseCode?: number;
+ responseText?: string;
+ responseXML?: XMLDocument;
+ }
+ /**
+ * Data passed to ajax event callbacks.
+ */
+ export interface AjaxEvent extends AjaxData {
+ type: "event";
+ status: AjaxEventStatus;
+ }
+ /**
+ * Data passed to ajax error callbacks.
+ */
+ export interface AjaxError extends AjaxData {
+ type: "error";
+ status: AjaxErrorStatus | "clientError" | "timeout";
+ errorName?: string;
+ errorMessage?: string;
+ /** @deprecated MyFaces compatibility alias. */
+ serverErrorName?: string;
+ /** @deprecated MyFaces compatibility alias. */
+ serverErrorMessage?: string;
+ /** MyFaces compatibility detail. */
+ description?: string;
+ /** MyFaces compatibility detail. */
+ typeDetails?: unknown;
+ }
+ /**
+ * Version of the implementation for the faces.ts.
+ *
+ * as specified within the jsf specifications faces.html:
+ *
+ * - left two digits major release number
+ * - middle two digits minor spec release number
+ * - right two digits bug release number
+ *
+ */
+ export const specversion: number;
+ /**
+ * Implementation version as specified within the jsf specification.
+ *
+ * A number increased with every implementation version
+ * and reset by moving to a new spec release number
+ *
+ */
+ export const implversion: number;
+ /**
+ * SeparatorChar as defined by facesContext.getNamingContainerSeparatorChar()
+ */
+ export const separatorchar: string;
+ /**
+ * Context Path as defined externalContext.requestContextPath
+ */
+ export const contextpath: string;
+ /**
+ * This method is responsible for the return of a given project stage as defined
+ * by the jsf specification.
+ *
+ * Valid return values are:
+ *
+ * - "Production"
+ * - "Development"
+ * - "SystemTest"
+ * - "UnitTest"
+ *
+ *
+ * @return {String} the current project state emitted by the server side method:
+ * jakarta.faces.application.Application.getProjectStage()
+ */
+ export function getProjectStage(): ProjectStage;
+ /**
+ * collect and encode data for a given form element (must be of type form)
+ * find the jakarta.faces.ViewState element and encode its value as well!
+ * return a concatenated string of the encoded values!
+ *
+ * @throws an exception in case of the given element not being of type form!
+ * https://issues.apache.org/jira/browse/MYFACES-2110
+ */
+ export function getViewState(formElement: Element | string): string;
+ /**
+ * returns the window identifier for the given node / window
+ * @return the window identifier or null if none is found
+ * @param rootNode
+ */
+ export function getClientWindow(rootNode?: Element | string): string | null;
+ export namespace ajax {
+ /**
+ * Callback signature for ajax lifecycle events.
+ */
+ export type OnEventCallback = (data: AjaxEvent) => void;
+ /**
+ * Callback signature for ajax errors.
+ */
+ export type OnErrorCallback = (data: AjaxError) => void;
+ /**
+ * Options object for faces.ajax.request.
+ */
+ export interface RequestOptions {
+ execute?: string;
+ render?: string;
+ onevent?: OnEventCallback;
+ onerror?: OnErrorCallback;
+ params?: Record;
+ delay?: number | "none";
+ resetValues?: boolean;
+ /** MyFaces extension/pass-through compatibility. */
+ [key: string]: any;
+ }
+ /**
+ * Per-request context object passed to faces.ajax.response.
+ */
+ export interface RequestContext {
+ sourceid?: string;
+ onerror?: OnErrorCallback;
+ onevent?: OnEventCallback;
+ /** MyFaces extension/pass-through compatibility. */
+ [key: string]: any;
+ }
+ /**
+ * this function has to send the ajax requests
+ *
+ * following request conditions must be met:
+ *
+ * - the request must be sent asynchronously!
+ * - the request must be a POST!!! request
+ * - the request url must be the form action attribute
+ * - all requests must be queued with a client side request queue to ensure the request ordering!
+ *
+ *
+ * @param {String|Node} element: any dom element no matter being it html or jsf, from which the event is emitted
+ * @param {EVENT} event: any javascript event supported by that object
+ * @param {Map} options : map of options being pushed into the ajax cycle
+ */
+ export function request(element: Element | string, event?: Event | null, options?: RequestOptions): void;
+ /**
+ * response handler
+ * @param request the request object having triggered this response
+ * @param context the request context
+ *
+ */
+ export function response(request: XMLHttpRequest, context?: RequestContext): void;
+ /**
+ * Adds an error handler to our global error queue.
+ * the error handler must be of the format function errorListener(<errorData>)
+ * with errorData being of following format:
+ *
+ * - errorData.type : "error"
+ * - errorData.status : the error status message
+ * - errorData.serverErrorName : the server error name in case of a server error
+ * - errorData.serverErrorMessage : the server error message in case of a server error
+ * - errorData.source : the issuing source element which triggered the request
+ * - eventData.responseCode: the response code (aka http request response code, 401 etc...)
+ * - eventData.responseText: the request response text
+ * - eventData.responseXML: the request response xml
+ *
+ *
+ * @param errorFunc error handler must be of the format function errorListener(<errorData>)
+ */
+ export function addOnError(errorFunc: OnErrorCallback): void;
+ /**
+ * Adds a global event listener to the ajax event queue. The event listener must be a function
+ * of following format: function eventListener(<eventData>)
+ *
+ * @param eventFunc event must be of the format function eventListener(<eventData>)
+ */
+ export function addOnEvent(eventFunc: OnEventCallback): void;
+ }
+ export namespace util {
+ /**
+ * varargs function which executes a chain of code (functions or any other code)
+ *
+ * if any of the code returns false, the execution
+ * is terminated prematurely skipping the rest of the code!
+ *
+ * @param {HTMLElement | String} source, the callee object
+ * @param {Event} event, the event object of the callee event triggering this function
+ * @param funcs ... arbitrary array of functions or strings
+ * @returns true if the chain has succeeded false otherwise
+ */
+ export function chain(source: HTMLElement | string, event?: Event | null, ...funcs: Array): boolean;
+ }
+ export namespace push {
+ /**
+ * Invoked when the websocket is opened.
+ */
+ export type OnOpenHandler = (channel: string) => void;
+ /**
+ * Invoked when a message is received from the server.
+ */
+ export type OnMessageHandler = (message: unknown, channel: string, event: MessageEvent) => void;
+ /**
+ * Invoked when a connection error occurs and the websocket will attempt to reconnect.
+ */
+ export type OnErrorHandler = (code: number, channel: string, event: CloseEvent) => void;
+ /**
+ * Invoked when the websocket is closed and will not attempt to reconnect.
+ */
+ export type OnCloseHandler = (code: number, channel: string, event: CloseEvent) => void;
+ /**
+ * @param socketClientId the sockets client identifier
+ * @param url the uri to reach the socket
+ * @param channel the channel name/id
+ * @param onopen The function to be invoked when the web socket is opened.
+ * @param onmessage The function to be invoked when a message is received.
+ * @param onerror The function to be invoked when an error occurs.
+ * @param onclose The function to be invoked when the web socket is closed.
+ * @param behaviors functions which are invoked whenever a message is received
+ * @param autoConnect Whether or not to automatically open the socket. Defaults to false.
+ */
+ export function init(socketClientId: string, url: string, channel: string, onopen: OnOpenHandler | string | null, onmessage: OnMessageHandler | string | null, onerror: OnErrorHandler | string | null, onclose: OnCloseHandler | string | null, behaviors: Record void>>, autoConnect: boolean): void;
+ /**
+ * Open the web socket on the given channel.
+ * @param socketClientId The name of the web socket channel.
+ * @throws Error is thrown, if the channel is unknown.
+ */
+ export function open(socketClientId: string): void;
+ /**
+ * Close the web socket on the given channel.
+ * @param socketClientId The id of the web socket client.
+ * @throws Error is thrown, if the channel is unknown.
+ */
+ export function close(socketClientId: string): void;
+ }
+}
+
+declare namespace myfaces {
+ /**
+ * AB function similar to mojarra and Primefaces
+ * not part of the spec but a convenience accessor method
+ * Code provided by Thomas Andraschko
+ *
+ * @param source the event source
+ * @param event the event
+ * @param eventName event name for java.jakarta.faces.behavior.event
+ * @param execute execute list as passed down in faces.ajax.request
+ * @param render the render list as string
+ * @param options the options which need to be merged in
+ * @param userParameters a set of user parameters which go into the final options under params, they can override whatever is passed via options
+ */
+ export function ab(source: Element, event: Event, eventName: string, execute: string, render: string, options?: faces.ajax.RequestOptions, userParameters?: faces.ajax.RequestOptions): void;
+ /**
+ * Helper function in the myfaces namespace to handle document ready properly for the load case
+ * the ajax case, does not need proper treatment, since it is deferred anyway.
+ * Used by command script as helper function!
+ *
+ * @param executionFunc the function to be executed upon ready
+ */
+ export function onDomReady(executionFunc: () => void): void;
+ /**
+ * reserve a namespace for the given string
+ * @param namespace the namespace to reserve with '.' as separator
+ */
+ export function reserveNamespace(namespace: string): void;
+ /**
+ * legacy oam functions
+ */
+ export const oam: typeof oam;
+}
diff --git a/api/src/client/typescript/faces/@types/definitions/index.d.ts b/api/src/client/typescript/faces/@types/definitions/index.d.ts
index 5854ac6a31..f38eb10471 100644
--- a/api/src/client/typescript/faces/@types/definitions/index.d.ts
+++ b/api/src/client/typescript/faces/@types/definitions/index.d.ts
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+import type { faces, myfaces } from '../../api/_api';
+
/**
- * Basic internal types used
- *
- * This file is only there to allow global calls into window, faces and ajax
- * in a typesafe manner, hence eliminating any casts.
+ * Internal utility types used throughout the implementation.
+ * Public API types live in _api.ts; these are build-only helpers.
*/
declare global {
@@ -28,149 +28,38 @@ declare global {
type AssocArr = { [key: string]: T };
type EvalFuncs = Array;
-
type Options = {
render ?: string,
- execute ?: string, //space separated list of client ids
- onevent ?: Function, // event handler callback
- onerror ?: Function, // error handler callback
- params ?: AssocArr, // passthrough params
- delay ?: number, // delay in milliseconds
- resetValues ?: boolean, // if set to true jakarta.faces.partial.resetValues is sent
- /* @deprecated non-spec conform fallback behavior that anything can be passed and is used as passthrough */
+ execute ?: string,
+ onevent ?: Function,
+ onerror ?: Function,
+ params ?: AssocArr,
+ delay ?: number,
+ resetValues ?: boolean,
[key: string]: any
}
type Context = AssocArr;
type ElemDef = Element | string;
- /**
- * *
- * - errorData.type : "error"
- * - errorData.status : the error status message
- * - errorData.serverErrorName : the server error name in case of a server error
- * - errorData.serverErrorMessage : the server error message in case of a server error
- * - errorData.source : the issuing source element which triggered the request
- * - eventData.responseCode: the response code (aka http request response code, 401 etc...)
- * - eventData.responseText: the response text
- * - eventData.responseXML: the response xml
- *
- */
- interface IErrorData {
- type: any;
- status: string | null;
- serverErrorName: string;
- serverErrorMessage: string;
- source: any;
- responseCode: number;
- responseText: string;
- responseXML: string;
- }
-
- /**
- *
- * - status: status of the ajax cycle
- *
- */
- interface IEventData {
- status: string | null;
- source: any;
- }
-
- interface Ajax {
- request(element: Element, event?: Event, options?: Options): void;
- response(request: XMLHttpRequest, context?: Context): void;
- }
-
- interface Util {
- chain(source, event, ...funcs: Array): boolean;
- }
-
- interface Push {
- init(socketClientId: string,
- uri: string,
- channel: string,
- onopen: Function,
- onmessage: Function,
- onerror: Function,
- onclose: Function,
- behaviorScripts: any,
- autoconnect: boolean): void;
- init(socketClientId: string,
- uri: string,
- channel: string,
- onopen: Function,
- onmessage: Function,
- onclose: Function,
- behaviorScripts: any,
- autoconnect: boolean): void;
- open(socketClientId: string);
- close(socketClientId: string): void;
- }
-
- interface FacesAPI {
- contextpath: string;
- specversion: number;
- implversion: number;
- separatorchar: string;
-
- getProjectStage(): string;
- getViewState(formElement: Element | string): string;
- getClientWindow(rootNode?: Element | string): string;
- getSeparatorChar(): string;
- response(request: XMLHttpRequest, context?: Context): void;
- addOnError(errorFunc: (data: IErrorData) => void): void;
- addOnEvent(eventFunc: (data: IEventData) => void): void;
-
- ajax: Ajax;
- util: Util;
- push: Push;
- }
-
- interface OAM {
- clearHiddenInput(formName: string, name: string): void;
- setHiddenInput(formName: string, name: string, value: string): void;
- submitForm(formName: string, linkId?: string |null, target?: string | null, params?: AssocArr |Tuples | null): boolean;
- }
-
- interface MyFacesAPI {
- [key: string]: any;
- ab(source: Element, event: Event, eventName: string, execute: string, render: string, options?: Context, userParams?: Context): void;
- reserveNamespace(namespace: string): void;
-
- config: { [key: string]: any };
- oam: OAM;
- core: {
- config ?: {[key: string]: any};
- };
- }
-
-
-
/*
- * Global namespaces type definitions
+ * Global namespace type definitions — typed from the canonical _api.ts source.
*/
- let myfaces: MyFacesAPI;
- let jsf: FacesAPI;
- let faces: FacesAPI;
+ let faces: typeof faces;
+ let jsf: typeof faces;
+ let myfaces: typeof myfaces;
- // special "magic", Typescript merges whatever we have
- // to window. This is a language "hack", but documented.
// see https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html
- // lib.dom.d.ts declares the type Window as being type for window.
// noinspection JSUnusedGlobalSymbols
interface Window {
- [key: string]: any,
- myfaces: MyFacesAPI,
- faces: FacesAPI,
- jsf: FacesAPI,
- XMLHttpRequest: XMLHttpRequest,
- called: { [key: string]: any }
+ [key: string]: any;
+ faces: typeof faces;
+ jsf: typeof faces;
+ myfaces: typeof myfaces;
+ XMLHttpRequest: XMLHttpRequest;
+ called: { [key: string]: any };
}
}
-// this is needed to tell the compiler that we have an ambient
-// module, otherwise the global overload would produce an error
-// https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html
-// noinspection JSUnusedGlobalSymbols
-export var __my_faces_ambient_module_glob_;
+// needed to make this file a module so the declare global block is valid
+export {};
diff --git a/api/src/client/typescript/faces/api/_api.ts b/api/src/client/typescript/faces/api/_api.ts
index dfa2576456..83ff20491b 100644
--- a/api/src/client/typescript/faces/api/_api.ts
+++ b/api/src/client/typescript/faces/api/_api.ts
@@ -17,13 +17,62 @@ import {Implementation} from "../impl/AjaxImpl";
import {PushImpl} from "../impl/PushImpl";
import {oam as _oam} from "../myfaces/OamSubmit";
import {$nsp, CTX_OPTIONS_EXECUTE, CTX_OPTIONS_PARAMS, CTX_PARAM_RENDER, P_BEHAVIOR_EVENT} from "../impl/core/Const";
-import {ErrorData} from "../impl/xhrCore/ErrorData";
-import {EventData} from "../impl/xhrCore/EventData";
//we use modules to get a proper jsdoc and static/map structure in the calls
//as per spec requirement
export namespace faces {
+ /**
+ * Project stage values, mirroring jakarta.faces.application.ProjectStage.
+ */
+ export type ProjectStage = "Development" | "UnitTest" | "SystemTest" | "Production";
+
+ /**
+ * Status values sent to ajax event callbacks.
+ */
+ export type AjaxEventStatus = "begin" | "complete" | "success";
+
+ /**
+ * Status values sent to ajax error callbacks.
+ */
+ export type AjaxErrorStatus = "httpError" | "emptyResponse" | "malformedXML" | "serverError";
+
+ /**
+ * Common shape for the data passed to ajax callbacks.
+ */
+ export interface AjaxData {
+ source?: Element;
+ responseCode?: number;
+ responseText?: string;
+ responseXML?: XMLDocument;
+ }
+
+ /**
+ * Data passed to ajax event callbacks.
+ */
+ export interface AjaxEvent extends AjaxData {
+ type: "event";
+ status: AjaxEventStatus;
+ }
+
+ /**
+ * Data passed to ajax error callbacks.
+ */
+ export interface AjaxError extends AjaxData {
+ type: "error";
+ status: AjaxErrorStatus | "clientError" | "timeout";
+ errorName?: string;
+ errorMessage?: string;
+ /** @deprecated MyFaces compatibility alias. */
+ serverErrorName?: string;
+ /** @deprecated MyFaces compatibility alias. */
+ serverErrorMessage?: string;
+ /** MyFaces compatibility detail. */
+ description?: string;
+ /** MyFaces compatibility detail. */
+ typeDetails?: unknown;
+ }
+
/**
* Version of the implementation for the faces.ts.
@@ -35,7 +84,7 @@ export namespace faces {
* - right two digits bug release number
*
*/
- export var specversion = 400000;
+ export const specversion: number = 400000;
/**
* Implementation version as specified within the jsf specification.
*
@@ -43,18 +92,18 @@ export namespace faces {
* and reset by moving to a new spec release number
*
*/
- export var implversion = 0;
+ export const implversion: number = 0;
/**
* SeparatorChar as defined by facesContext.getNamingContainerSeparatorChar()
*/
- export var separatorchar: string = getSeparatorChar();
+ export const separatorchar: string = getSeparatorChar();
// noinspection JSUnusedGlobalSymbols
/**
* Context Path as defined externalContext.requestContextPath
*/
- export var contextpath: string = '#{facesContext.externalContext.requestContextPath}';
+ export const contextpath: string = '#{facesContext.externalContext.requestContextPath}';
// we do not have a fallback here, for now
/**
@@ -72,8 +121,8 @@ export namespace faces {
* @return {String} the current project state emitted by the server side method:
* jakarta.faces.application.Application.getProjectStage()
*/
- export function getProjectStage(): string | null {
- return Implementation.getProjectStage();
+ export function getProjectStage(): ProjectStage {
+ return Implementation.getProjectStage() as ProjectStage;
}
/**
@@ -112,6 +161,42 @@ export namespace faces {
export namespace ajax {
"use strict";
+ /**
+ * Callback signature for ajax lifecycle events.
+ */
+ export type OnEventCallback = (data: AjaxEvent) => void;
+
+ /**
+ * Callback signature for ajax errors.
+ */
+ export type OnErrorCallback = (data: AjaxError) => void;
+
+ /**
+ * Options object for faces.ajax.request.
+ */
+ export interface RequestOptions {
+ execute?: string;
+ render?: string;
+ onevent?: OnEventCallback;
+ onerror?: OnErrorCallback;
+ params?: Record;
+ delay?: number | "none";
+ resetValues?: boolean;
+ /** MyFaces extension/pass-through compatibility. */
+ [key: string]: any;
+ }
+
+ /**
+ * Per-request context object passed to faces.ajax.response.
+ */
+ export interface RequestContext {
+ sourceid?: string;
+ onerror?: OnErrorCallback;
+ onevent?: OnEventCallback;
+ /** MyFaces extension/pass-through compatibility. */
+ [key: string]: any;
+ }
+
/**
* this function has to send the ajax requests
*
@@ -127,8 +212,8 @@ export namespace faces {
* @param {EVENT} event: any javascript event supported by that object
* @param {Map} options : map of options being pushed into the ajax cycle
*/
- export function request(element: Element, event?: Event, options?: Options): void {
- Implementation.request(element, event, options)
+ export function request(element: Element | string, event?: Event | null, options?: RequestOptions): void {
+ Implementation.request(element, event as any, options as any)
}
/**
@@ -137,7 +222,7 @@ export namespace faces {
* @param context the request context
*
*/
- export function response(request: XMLHttpRequest, context?: Context): void {
+ export function response(request: XMLHttpRequest, context?: RequestContext): void {
Implementation.response(request, context as any);
}
@@ -158,7 +243,7 @@ export namespace faces {
*
* @param errorFunc error handler must be of the format function errorListener(<errorData>)
*/
- export function addOnError(errorFunc: (data: ErrorData) => void): void {
+ export function addOnError(errorFunc: OnErrorCallback): void {
Implementation.addOnError(errorFunc as any);
}
@@ -168,7 +253,7 @@ export namespace faces {
*
* @param eventFunc event must be of the format function eventListener(<eventData>)
*/
- export function addOnEvent(eventFunc: (data: EventData) => void): void {
+ export function addOnEvent(eventFunc: OnEventCallback): void {
Implementation.addOnEvent(eventFunc as any);
}
}
@@ -186,12 +271,32 @@ export namespace faces {
* @param funcs ... arbitrary array of functions or strings
* @returns true if the chain has succeeded false otherwise
*/
- export function chain(source: HTMLElement | string, event: Event | null, ...funcs: Array): boolean {
- return Implementation.chain(source, event, ...(funcs as EvalFuncs));
+ export function chain(source: HTMLElement | string, event?: Event | null, ...funcs: Array): boolean {
+ return Implementation.chain(source, event ?? null, ...(funcs as EvalFuncs));
}
}
export namespace push {
+ /**
+ * Invoked when the websocket is opened.
+ */
+ export type OnOpenHandler = (channel: string) => void;
+
+ /**
+ * Invoked when a message is received from the server.
+ */
+ export type OnMessageHandler = (message: unknown, channel: string, event: MessageEvent) => void;
+
+ /**
+ * Invoked when a connection error occurs and the websocket will attempt to reconnect.
+ */
+ export type OnErrorHandler = (code: number, channel: string, event: CloseEvent) => void;
+
+ /**
+ * Invoked when the websocket is closed and will not attempt to reconnect.
+ */
+ export type OnCloseHandler = (code: number, channel: string, event: CloseEvent) => void;
+
/**
* @param socketClientId the sockets client identifier
* @param url the uri to reach the socket
@@ -206,13 +311,13 @@ export namespace faces {
export function init(socketClientId: string,
url: string,
channel: string,
- onopen: PushImpl.OpenCallback | string,
- onmessage: PushImpl.MessageCallback | string,
- onerror: PushImpl.ErrorCallback | string,
- onclose: PushImpl.CloseCallback | string,
- behaviors: any,
+ onopen: OnOpenHandler | string | null,
+ onmessage: OnMessageHandler | string | null,
+ onerror: OnErrorHandler | string | null,
+ onclose: OnCloseHandler | string | null,
+ behaviors: Record void>>,
autoConnect: boolean): void {
- PushImpl.init(socketClientId, url, channel, onopen, onmessage, onerror, onclose, behaviors, autoConnect);
+ PushImpl.init(socketClientId, url, channel, onopen as any, onmessage as any, onerror as any, onclose as any, behaviors, autoConnect);
}
/**
@@ -250,7 +355,7 @@ export namespace myfaces {
* @param options the options which need to be merged in
* @param userParameters a set of user parameters which go into the final options under params, they can override whatever is passed via options
*/
- export function ab(source: Element, event: Event, eventName: string, execute: string, render: string, options: Options = {}, userParameters: Options = {}): void {
+ export function ab(source: Element, event: Event, eventName: string, execute: string, render: string, options: faces.ajax.RequestOptions = {}, userParameters: faces.ajax.RequestOptions = {}): void {
if(!options) {
options = {};
}
@@ -277,7 +382,7 @@ export namespace myfaces {
options["params"][key] = userParameters[key];
}
- (window?.faces ?? window.jsf).ajax.request(source, event, options);
+ (window?.faces ?? window.jsf).ajax.request(source, event, options as any);
}
diff --git a/api/src/client/typescript/faces/api/_api_ae_stub.d.ts b/api/src/client/typescript/faces/api/_api_ae_stub.d.ts
new file mode 100644
index 0000000000..4ced4c3ef1
--- /dev/null
+++ b/api/src/client/typescript/faces/api/_api_ae_stub.d.ts
@@ -0,0 +1,21 @@
+
+/*! Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.
+ */
+// Stub file consumed only by tsconfig.ae.json (api-extractor's compiler config).
+// api-extractor uses mainEntryPointFilePath (target/dts/_api.d.ts) as the real
+// entry — this file exists solely to satisfy TypeScript's "at least one input"
+// requirement without pulling any source .ts files into the compiler program.
+export {};
diff --git a/api/src/client/typescript/faces/impl/PushImpl.ts b/api/src/client/typescript/faces/impl/PushImpl.ts
index c23ad9035c..69dd4371fe 100644
--- a/api/src/client/typescript/faces/impl/PushImpl.ts
+++ b/api/src/client/typescript/faces/impl/PushImpl.ts
@@ -49,13 +49,17 @@ export namespace PushImpl {
autoconnect: boolean;
};
+ /** @internal */
export let sockets: {[key: string]: Socket} = {};
/* component attributes by clientId */
+ /** @internal */
export let components: {[key: string]: ComponentData} = {};
/* client ids by token (share websocket connection) */
+ /** @internal */
export let clientIdsByTokens: {[key: string]: string[]} = {};
// needed for testing
+ /** @internal */
export function reset() {
Object.values(sockets).forEach(s => { try { s.close(); } catch(e) { /* ignore */ } });
sockets = {};
diff --git a/api/src/client/typescript/faces/impl/core/Const.ts b/api/src/client/typescript/faces/impl/core/Const.ts
index 5768dd4c98..8bb8acd81b 100644
--- a/api/src/client/typescript/faces/impl/core/Const.ts
+++ b/api/src/client/typescript/faces/impl/core/Const.ts
@@ -206,8 +206,8 @@ export const UNKNOWN = "UNKNOWN";
* changed to a simple value passthrough
*/
-export function $faces(): FacesAPI {
- return (window?.faces ?? window?.jsf) as FacesAPI;
+export function $faces(): typeof faces {
+ return window?.faces ?? window?.jsf;
}
export function $nsp(inputNamespace?: any): any {
diff --git a/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts b/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts
index 9eddce5b9a..1832024cb3 100644
--- a/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts
+++ b/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts
@@ -46,7 +46,7 @@ export enum ErrorType {
* everything into the same attributes,
* I will add deprecated myfaces backwards compatibility attributes as well
*/
-export class ErrorData extends EventData implements IErrorData {
+export class ErrorData extends EventData {
type: string = "error";
source: string | Element;
@@ -64,6 +64,7 @@ export class ErrorData extends EventData implements IErrorData {
serverErrorMessage!: string;
description: string;
+ /** @internal */
constructor(source: string | Element, errorName: string, errorMessage: string, responseText: string | null = null, responseXML: Document | null = null, responseCode: number = -1, statusOverride: string | null = null, type = ErrorType.CLIENT_ERROR) {
super();
@@ -91,15 +92,18 @@ export class ErrorData extends EventData implements IErrorData {
}
}
+ /** @internal */
static fromClient(e: Error): ErrorData {
return new ErrorData((e as any)?.source ?? "client", e?.name ?? EMPTY_STR, e?.message ?? EMPTY_STR, e?.stack ?? EMPTY_STR);
}
+ /** @internal */
static fromHttpConnection(source: any, name: string, message: string, responseText: string, responseXML: Document | null, responseCode: number, status: string = EMPTY_STR): ErrorData {
return new ErrorData(source, name, message, responseText, responseXML, responseCode, status, ErrorType.HTTP_ERROR);
}
- static fromGeneric(context: Config, errorCode: number, errorType: ErrorType = ErrorType.SERVER_ERROR): ErrorData {
+ /** @internal */
+ static fromGeneric(context: any, errorCode: number, errorType: ErrorType = ErrorType.SERVER_ERROR): ErrorData {
let getMsg = this.getMsg;
@@ -114,11 +118,12 @@ export class ErrorData extends EventData implements IErrorData {
return new ErrorData(source, errorName, errorMessage, responseText, responseXML, errorCode, status, errorType);
}
- private static getMsg(context: Config, param: string) {
+ private static getMsg(context: any, param: string) {
return getMessage(context.getIf(param).orElse(EMPTY_STR).value);
}
- static fromServerError(context: Config): ErrorData {
+ /** @internal */
+ static fromServerError(context: any): ErrorData {
return this.fromGeneric(context, -1);
}
diff --git a/api/src/client/typescript/faces/impl/xhrCore/EventData.ts b/api/src/client/typescript/faces/impl/xhrCore/EventData.ts
index 20a64521db..51bb8ca509 100644
--- a/api/src/client/typescript/faces/impl/xhrCore/EventData.ts
+++ b/api/src/client/typescript/faces/impl/xhrCore/EventData.ts
@@ -16,7 +16,7 @@
import {Config, DQ} from "mona-dish";
import {BEGIN, CTX_PARAM_REQ_PASS_THR, EVENT, P_AJAX_SOURCE, SOURCE} from "../core/Const";
-export class EventData implements IEventData{
+export class EventData {
type!: string;
status!: string | null;
source: any;
@@ -24,7 +24,8 @@ export class EventData implements IEventData{
responseText!: string;
responseXML!: Document | null;
- static createFromRequest(request: XMLHttpRequest, internalContext: Config, context: Config, /*event name*/ name: string): EventData {
+ /** @internal */
+ static createFromRequest(request: XMLHttpRequest, internalContext: any, context: any, /*event name*/ name: string): EventData {
let eventData = new EventData();
let internalSource = "_internal._source";
diff --git a/api/src/client/typescript/faces/myfaces/OamSubmit.ts b/api/src/client/typescript/faces/myfaces/OamSubmit.ts
index fec6444acf..9bf6b33df0 100644
--- a/api/src/client/typescript/faces/myfaces/OamSubmit.ts
+++ b/api/src/client/typescript/faces/myfaces/OamSubmit.ts
@@ -78,7 +78,7 @@ export namespace oam {
* @param target
* @param params
*/
- export const submitForm = function (formName: string, linkId: string | null = null, target: string |null = null, params: AssocArr | Tuples | null = {} ): boolean {
+ export const submitForm = function (formName: string, linkId: string | null = null, target: string |null = null, params: Record | [string, any][] | null = {} ): boolean {
//handle a possible incoming null, not sure if this is used that way anywhere, but we allow it
diff --git a/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts b/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts
index 7e568160da..290555d568 100644
--- a/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts
+++ b/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts
@@ -15,16 +15,34 @@
*/
// AI-generated: this file was created with assistance from Claude (Anthropic) — see AI_CONTRIBUTIONS.md
+import type { faces } from '../../api/_api';
+
/**
- * Compile-time type compatibility check for Push.init() overloads.
- *
- * This file is never executed. Its only purpose is to make the TypeScript
- * compiler verify that both supported call signatures of Push.init() are
- * accepted: the 4-callback form (onopen, onmessage, onerror, onclose) and
- * the legacy 3-callback form (onmessage, onerror, onclose). If either
- * overload is accidentally broken, tsc will reject this file.
+ * Compatibility interface covering both the faces 9-param form (with onerror)
+ * and the JSF 2.3 8-param form (without onerror). Both must be callable so
+ * that the runtime implementation works correctly in both the faces and JSF realms.
+ * This file is never executed — tsc rejecting it signals a broken signature.
*/
-function verifyPushInitTypeCompatibility(push: Push): void {
+interface PushCompat {
+ init(socketClientId: string, url: string, channel: string,
+ onopen: faces.push.OnOpenHandler | string | null,
+ onmessage: faces.push.OnMessageHandler | string | null,
+ onerror: faces.push.OnErrorHandler | string | null,
+ onclose: faces.push.OnCloseHandler | string | null,
+ behaviors: Record void>>,
+ autoConnect: boolean): void;
+ init(socketClientId: string, url: string, channel: string,
+ onopen: faces.push.OnOpenHandler | string | null,
+ onmessage: faces.push.OnMessageHandler | string | null,
+ onclose: faces.push.OnCloseHandler | string | null,
+ behaviors: Record void>>,
+ autoConnect: boolean): void;
+ open(socketClientId: string): void;
+ close(socketClientId: string): void;
+}
+
+function verifyPushInitTypeCompatibility(push: PushCompat): void {
+ // faces 4.0 form — with onerror
push.init("clientId1", "booga.ws", "mychannel",
() => {},
() => {},
@@ -34,6 +52,7 @@ function verifyPushInitTypeCompatibility(push: Push): void {
true
);
+ // JSF 2.3 legacy form — without onerror
push.init("clientId1", "booga.ws", "mychannel",
() => {},
() => {},
diff --git a/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts b/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts
index 1c4091c794..bd68da94cc 100644
--- a/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts
+++ b/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts
@@ -20,7 +20,8 @@ import {expect} from 'chai';
import * as sinon from 'sinon';
import {StandardInits} from "../frameworkBase/_ext/shared/StandardInits";
-import {CTX_PARAM_REQ_PASS_THR, P_EXECUTE, P_RENDER} from "../../impl/core/Const";
+import {CTX_PARAM_REQ_PASS_THR, IDENT_NONE, P_EXECUTE, P_RENDER} from "../../impl/core/Const";
+import {StateHolder} from "../../impl/core/ImplTypes";
const defaultMyFaces = StandardInits.defaultMyFaces;
import {_Es2019Array} from "mona-dish";
@@ -170,6 +171,22 @@ describe('faces.ajax.request test suite', () => {
})
+ it("execute @none must delete P_EXECUTE from the request pass-through context", () => {
+ const addRequestToQueue = sinon.stub(Implementation.queueHandler, "addRequestToQueue");
+ try {
+ DomQuery.byId("input_2").addEventListener("click", (event: Event) => {
+ faces.ajax.request(null, event, {execute: IDENT_NONE, render: IDENT_NONE});
+ }).click();
+
+ expect(addRequestToQueue.called).to.be.true;
+ const context = addRequestToQueue.args[0][2] as Config;
+ // remapDefaultConstants must have deleted P_EXECUTE when execute="@none"
+ expect(context.getIf(CTX_PARAM_REQ_PASS_THR, P_EXECUTE).isAbsent()).to.be.true;
+ } finally {
+ addRequestToQueue.restore();
+ }
+ });
+
it("sidebehavior chain on undefined must not break the chain only a dedicated false does", function() {
let called = {};
window.called = called;
@@ -230,3 +247,60 @@ describe('faces.ajax.request test suite', () => {
});
+describe('AjaxImpl.getClientWindow differenceCheck', () => {
+ let oldFlatMap = null;
+
+ beforeEach(async () => {
+ return await StandardInits.defaultMyFaces().then(() => {
+ oldFlatMap = Array.prototype["flatMap"];
+ (window as any)["Es2019Array"] = _Es2019Array;
+ delete Array.prototype["flatMap"];
+ });
+ });
+
+ afterEach(() => {
+ if (oldFlatMap) {
+ Array.prototype["flatMap"] = oldFlatMap;
+ oldFlatMap = null;
+ }
+ });
+
+ it("must throw when two forms have different windowIds", () => {
+ document.body.innerHTML += `
+
+ `;
+
+ expect(() => faces.getClientWindow(document.body)).to.throw();
+ });
+
+ it("must return the windowId when all forms agree", () => {
+ document.body.innerHTML = `
+ `;
+
+ const result = faces.getClientWindow(document.body);
+ expect(result).to.eq("samewin");
+ });
+});
+
+describe('StateHolder.hasNameSpace', () => {
+ it('returns false when id has no namespace prefix', () => {
+ const s = new StateHolder("jakarta.faces.ViewState", "val");
+ expect(s.hasNameSpace).to.be.false;
+ expect(s.nameSpace).to.eq("");
+ });
+
+ it('returns true when id has a namespace prefix', () => {
+ const s = new StateHolder("myForm:jakarta.faces.ViewState", "val");
+ expect(s.hasNameSpace).to.be.true;
+ expect(s.nameSpace).to.eq("myForm");
+ });
+});
diff --git a/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts b/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts
index 2aef1186a6..8b455d3308 100644
--- a/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts
+++ b/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts
@@ -85,6 +85,36 @@ describe('ExtDomQuery test suite', () => {
const text = document.createTextNode("inline text");
expect(() => new ExtDomQuery(text as any).runHeadInserts()).not.to.throw();
});
+
+ it('runHeadInserts with suppressDoubleIncludes=false inserts duplicate link resources', () => {
+ const href = "/test-res-no-dedup.css";
+ const existing = document.createElement("link");
+ existing.setAttribute("href", href);
+ document.head.appendChild(existing);
+
+ const before = document.head.querySelectorAll(`link[href="${href}"]`).length;
+
+ const dup = document.createElement("link");
+ dup.setAttribute("href", href);
+ new ExtDomQuery(dup as any).runHeadInserts(false);
+
+ expect(document.head.querySelectorAll(`link[href="${href}"]`).length).to.be.gt(before);
+ });
+
+ it('runHeadInserts with suppressDoubleIncludes=true skips duplicate link resources', () => {
+ const href = "/test-res-with-dedup.css";
+ const existing = document.createElement("link");
+ existing.setAttribute("href", href);
+ document.head.appendChild(existing);
+
+ const before = document.head.querySelectorAll(`link[href="${href}"]`).length;
+
+ const dup = document.createElement("link");
+ dup.setAttribute("href", href);
+ new ExtDomQuery(dup as any).runHeadInserts(true);
+
+ expect(document.head.querySelectorAll(`link[href="${href}"]`).length).to.eq(before);
+ });
});
describe('ExtConfig', () => {
@@ -124,4 +154,5 @@ describe('ExtConfig', () => {
copy.assign("name").value = "Bob";
expect(config.getIf("name").value).to.eq("Alice");
});
+
});
diff --git a/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts b/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts
index ee35f15eff..128604324c 100644
--- a/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts
+++ b/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts
@@ -39,6 +39,11 @@ describe("ExtLang", function () {
expect(ExtLang.getMessage("missing.key")).to.eq("missing.key");
});
+ it("must return a message string when the key exists in Messages", function () {
+ // MSG_TEST is defined in Messages.ts as "Testmessage" — covers the left branch of ?? defaultMessage ?? key
+ expect(ExtLang.getMessage("MSG_TEST")).to.eq("Testmessage");
+ });
+
it("must read global and local myfaces config with local priority", function () {
window.myfaces.config = {...window.myfaces.config, delay: 50, timeout: 100};
diff --git a/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts b/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts
index e99a52f742..5ff852467e 100644
--- a/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts
+++ b/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts
@@ -151,5 +151,44 @@ describe('Tests on the xhr core when it starts to call the request', function ()
expect(submit_spy.called).to.eq(false);
})
+ it('setHiddenInput must update the value when the field already exists', function () {
+ let FORM_ID = "blarg";
+ setHiddenInput(FORM_ID, "existing_field", "first_value");
+ setHiddenInput(FORM_ID, "existing_field", "updated_value");
+ const inputs = DomQuery.byId(FORM_ID).querySelectorAll("input[name='existing_field']");
+ expect(inputs.length).to.eq(1, "must not create a duplicate input");
+ expect(inputs.inputValue.value).to.eq("updated_value", "must update the value in-place");
+ });
+
+ it('clearHiddenInput must not throw when the field does not exist', function () {
+ let FORM_ID = "blarg";
+ expect(() => clearHiddenInput(FORM_ID, "nonexistent_field")).not.to.throw();
+ });
+
+ it('submitForm must work with array-style params', function () {
+ let FORM_ID = "blarg";
+ let form = DomQuery.byId(FORM_ID);
+ const submit_spy = Sinon.spy(() => {});
+ (form.value.value as any).submit = submit_spy;
+
+ submitForm(FORM_ID, 'mylink', null, [["arrKey1", "arrVal1"], ["arrKey2", "arrVal2"]]);
+
+ expect(submit_spy.called).to.eq(true);
+ form = DomQuery.byId(FORM_ID);
+ expect(form.querySelectorAll("input[name='arrKey1']").isAbsent()).to.eq(true, "params must be cleaned up after submit");
+ expect(form.querySelectorAll("input[name='arrKey2']").isAbsent()).to.eq(true, "params must be cleaned up after submit");
+ });
+
+ it('submitForm must work when params is null', function () {
+ let FORM_ID = "blarg";
+ let form = DomQuery.byId(FORM_ID);
+ const submit_spy = Sinon.spy(() => {});
+ (form.value.value as any).submit = submit_spy;
+
+ submitForm(FORM_ID, null, null, null);
+
+ expect(submit_spy.called).to.eq(true);
+ });
+
// further tests will follow if needed, for now the namespace must be restored
});
diff --git a/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts b/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts
index 3bfbfb883f..e3fd3f1d92 100644
--- a/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts
+++ b/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts
@@ -25,7 +25,7 @@ import {StandardInits} from "../frameworkBase/_ext/shared/StandardInits";
import {Implementation} from "../../impl/AjaxImpl";
const defaultMyFaces = StandardInits.defaultMyFaces;
import {XhrQueueController} from "../../impl/util/XhrQueueController";
-import {IAsyncRunnable} from "../../impl/util/AsyncRunnable";
+import {AsyncRunnable, IAsyncRunnable} from "../../impl/util/AsyncRunnable";
class ControlledRunnable implements IAsyncRunnable {
started = 0;
@@ -247,3 +247,51 @@ describe('Asynchronous Queue tests', () => {
}
});
});
+
+class SimpleRunnable extends AsyncRunnable {
+ private _shouldResolve: boolean;
+ constructor(resolve = true) { super(); this._shouldResolve = resolve; }
+ cancel() {}
+ start() {
+ if (this._shouldResolve) this.resolve(true);
+ else this.reject(new Error("rejected"));
+ }
+}
+
+describe('AsyncRunnable.finally() tests', () => {
+ it('finally callback must be called on resolve', function () {
+ const r = new SimpleRunnable(true);
+ let finallyCalled = false;
+ r.finally(() => { finallyCalled = true; });
+ r.start();
+ expect(finallyCalled).to.be.true;
+ });
+
+ it('finally callback must be called on reject', function () {
+ const r = new SimpleRunnable(false);
+ let finallyCalled = false;
+ r.catch(() => {}).finally(() => { finallyCalled = true; });
+ r.start();
+ expect(finallyCalled).to.be.true;
+ });
+
+ it('finally callback must be called in addition to then', function () {
+ const r = new SimpleRunnable(true);
+ let thenCalled = false;
+ let finallyCalled = false;
+ r.then(() => { thenCalled = true; }).finally(() => { finallyCalled = true; });
+ r.start();
+ expect(thenCalled).to.be.true;
+ expect(finallyCalled).to.be.true;
+ });
+
+ it('finally callback must be called in addition to catch', function () {
+ const r = new SimpleRunnable(false);
+ let catchCalled = false;
+ let finallyCalled = false;
+ r.catch(() => { catchCalled = true; }).finally(() => { finallyCalled = true; });
+ r.start();
+ expect(catchCalled).to.be.true;
+ expect(finallyCalled).to.be.true;
+ });
+});
diff --git a/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts b/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts
index a062347659..07855e1a55 100644
--- a/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts
+++ b/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts
@@ -18,7 +18,7 @@ import {describe, it} from "mocha";
import * as sinon from "sinon";
import {expect} from "chai";
import {StandardInits} from "../frameworkBase/_ext/shared/StandardInits";
-import {_Es2019Array, DomQuery, DQ$} from "mona-dish";
+import {_Es2019Array, Config, DomQuery, DQ$} from "mona-dish";
import {
COMPLETE, EMPTY_STR,
EMPTY_RESPONSE,
@@ -31,8 +31,17 @@ import {
P_VIEWSTATE,
P_WINDOW_ID,
STATE_EVT_TIMEOUT,
- SUCCESS
+ SUCCESS,
+ CTX_PARAM_SRC_FRM_ID,
+ CTX_PARAM_REQ_PASS_THR,
+ CTX_PARAM_PPS,
+ IDENT_NONE,
+ REQ_TYPE_GET,
+ SOURCE,
} from "../../impl/core/Const";
+import {XhrRequest} from "../../impl/xhrCore/XhrRequest";
+import {ExtConfig} from "../../impl/util/ExtDomQuery";
+import {EventData} from "../../impl/xhrCore/EventData";
const defaultMyFaces = StandardInits.defaultMyFaces;
const initVirtualElement = StandardInits.initVirtualElement;
const STD_XML = StandardInits.STD_XML;
@@ -904,6 +913,33 @@ describe('Tests after core when it hits response', function () {
});
});
+ it("must include a checked checkbox issuing item via appendIssuingItem when not already in form data", () => {
+ // Use a form where name != id so the checkbox is NOT pre-encoded under the issuing item ID
+ const waitForResult = initCheckboxForm();
+ return waitForResult.then(() => {
+ document.body.innerHTML += `
+ `;
+
+ const send = sinon.spy(XMLHttpRequest.prototype, "send");
+ try {
+ const element = document.getElementById("cb-issuing-item");
+ faces.ajax.request(element, null, {
+ execute: "@none",
+ render: "@none"
+ });
+
+ const body: string = send.args[0][0] as string;
+ // appendIssuingItem must have added the issuing element keyed by its ID
+ expect(body).to.include("cb-issuing-item");
+ } finally {
+ send.restore();
+ }
+ });
+ });
+
/**
* https://issues.apache.org/jira/browse/MYFACES-4638
*/
@@ -1165,3 +1201,79 @@ describe('XhrRequest error handling and lifecycle', function () {
}, 0);
});
});
+
+describe('XhrRequest GET method', function () {
+
+ beforeEach(async function () {
+ return defaultMyFaces().then((close) => {
+ this.xhr = nise.fakeXhr.useFakeXMLHttpRequest();
+ this.requests = [];
+ this.xhr.onCreate = (xhr: any) => this.requests.push(xhr);
+ (global as any).XMLHttpRequest = this.xhr;
+ window.XMLHttpRequest = this.xhr;
+ this.closeIt = () => {
+ (global as any).XMLHttpRequest = window.XMLHttpRequest = this.xhr.restore();
+ close();
+ };
+ });
+ });
+
+ afterEach(function () { this.closeIt(); });
+
+ it('must call send(null) for a GET request', function () {
+ const send = sinon.spy(XMLHttpRequest.prototype, "send");
+ try {
+ const internalCtx = new Config({});
+ internalCtx.assign(CTX_PARAM_SRC_FRM_ID).value = "blarg";
+ internalCtx.assign(CTX_PARAM_PPS).value = false;
+
+ const reqCtx = new ExtConfig({});
+ reqCtx.assign(CTX_PARAM_REQ_PASS_THR, P_EXECUTE).value = IDENT_NONE;
+
+ const request = new XhrRequest(reqCtx, internalCtx, 0, REQ_TYPE_GET);
+ request.start();
+
+ expect(this.requests[0].method).to.eq("GET");
+ expect(send.calledOnce).to.be.true;
+ expect(send.firstCall.args[0]).to.be.null;
+ } finally {
+ send.restore();
+ }
+ });
+});
+
+describe('EventData source resolution fallback', function () {
+
+ beforeEach(async function () {
+ return defaultMyFaces().then((close) => {
+ this.closeIt = close;
+ });
+ });
+
+ afterEach(function () { this.closeIt(); });
+
+ it('must resolve source from context when _source._element is absent', function () {
+ const fakeXhr = { status: 200, responseText: "", responseXML: null } as any;
+ const internalCtx = new Config({});
+
+ const reqCtx = new ExtConfig({});
+ reqCtx.assign(SOURCE).value = "input_2";
+
+ const event = EventData.createFromRequest(fakeXhr, internalCtx, reqCtx, "begin");
+
+ expect(event.source).to.not.be.null;
+ expect(event.source).to.not.be.undefined;
+ expect((event.source as HTMLElement).id).to.eq("input_2");
+ });
+
+ it('must store resolved source in internalContext for subsequent calls', function () {
+ const fakeXhr = { status: 200, responseText: "", responseXML: null } as any;
+ const internalCtx = new Config({});
+ const reqCtx = new ExtConfig({});
+ reqCtx.assign(SOURCE).value = "input_2";
+
+ EventData.createFromRequest(fakeXhr, internalCtx, reqCtx, "begin");
+
+ expect(internalCtx.getIf("_source", "_element").value).to.not.be.null;
+ });
+});
diff --git a/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts b/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts
index 7c9d844893..392194a5c0 100644
--- a/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts
+++ b/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts
@@ -722,4 +722,43 @@ describe('Tests the jsf websocket client side api on high level (generic test wi
done();
});
});
+
+ it("must close the socket when all components are gone after an error-close", function () {
+ const channelToken = "booga.ws";
+
+ faces.push.init("clientId_close_test", "booga.ws", "mychannel",
+ () => {},
+ () => {},
+ () => {},
+ () => {},
+ "",
+ true
+ );
+
+ // After push.init with autoConnect=true, Socket.open() has already assigned
+ // its handler to fakeWebsocket.onopen. Invoking it directly marks the socket
+ // as hasEverConnected=true, which is required for the subsequent abnormal close
+ // to take the reconnectable (non-terminal) path.
+ this.fakeWebsocket.onopen({});
+
+ // Remove the component from the registry so the channel has no valid
+ // components. notifyErrorAndPruneMissingComponents will splice the
+ // clientId out, leaving the array empty, then closeIfChannelHasNoComponents
+ // detects this and closes the socket.
+ delete this.pushImpl.components["clientId_close_test"];
+
+ const closeSpy = sinon.spy(this.fakeWebsocket, "close");
+ try {
+ // Abnormal close (1006) is reconnectable — goes through the
+ // notifyErrorAndPruneMissingComponents → closeIfChannelHasNoComponents path.
+ this.fakeWebsocket._close({code: 1006});
+
+ expect(this.pushImpl.clientIdsByTokens[channelToken].length,
+ "clientIds array must be empty after stale component pruning").to.eq(0);
+ expect(closeSpy.called || this.fakeWebsocket.readyState === 3,
+ "socket must be closed when channel has no remaining components").to.be.true;
+ } finally {
+ closeSpy.restore();
+ }
+ });
});
diff --git a/api/src/client/typescript/mona_dish/DomQuery.ts b/api/src/client/typescript/mona_dish/DomQuery.ts
index bad1d05081..c2dccae1f4 100644
--- a/api/src/client/typescript/mona_dish/DomQuery.ts
+++ b/api/src/client/typescript/mona_dish/DomQuery.ts
@@ -160,8 +160,9 @@ function waitUntilDom(root: DomQuery, condition: (element: DomQuery) => boolean,
observer!.observe(item, observableOpts)
})
} else { // fallback for legacy browsers without mutation observer
-
- let interval: any = setInterval(() => {
+ let interval: any;
+ let timeout: any;
+ interval = setInterval(() => {
let found = findElement(root, condition);
if (!!found) {
if (timeout) {
@@ -172,13 +173,12 @@ function waitUntilDom(root: DomQuery, condition: (element: DomQuery) => boolean,
success(new DomQuery(found || root));
}
}, options.interval);
- let timeout = setTimeout(() => {
+ timeout = setTimeout(() => {
if (interval) {
clearInterval(interval);
error(MUT_ERROR);
}
- }, options.timeout)
-
+ }, options.timeout);
}
});
}
@@ -202,7 +202,6 @@ export class ElementAttribute extends ValueEmbedder {
for (let cnt = 0; cnt < val.length; cnt++) {
val[cnt].setAttribute(this.name, value);
}
- val[0].setAttribute(this.name, value);
}
protected getClass(): any {
@@ -237,11 +236,11 @@ export class Style extends ValueEmbedder {
}
protected getClass(): any {
- return ElementAttribute;
+ return Style;
}
static fromNullable(value?: any, valueKey: string = "value"): ElementAttribute {
- return new ElementAttribute(value, valueKey);
+ return new Style(value, valueKey);
}
}
@@ -434,11 +433,11 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl
get elements(): DomQuery {
// a simple querySelectorAll should suffice
- return this.querySelectorAll("input, checkbox, select, textarea, fieldset");
+ return this.querySelectorAll("input, select, textarea, fieldset");
}
get deepElements(): DomQuery {
- let elemStr = "input, select, textarea, checkbox, fieldset";
+ let elemStr = "input, select, textarea, fieldset";
return this.querySelectorAllDeep(elemStr);
}
@@ -602,7 +601,6 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl
*/
static fromMarkup(markup: string): DomQuery {
- // https:// developer.mozilla.org/de/docs/Web/API/DOMParser license creative commons
const doc = document.implementation.createHTMLDocument("");
markup = trim(markup);
let lowerMarkup = markup.toLowerCase();
@@ -622,16 +620,15 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl
let dummyPlaceHolder = new DomQuery(document.createElement("div"));
// table needs special treatment due to the browsers auto creation
- if (startsWithTag(lowerMarkup, "thead") || startsWithTag(lowerMarkup, "tbody")) {
+ if (startsWithTag(lowerMarkup, "thead")
+ || startsWithTag(lowerMarkup, "tbody")
+ || startsWithTag(lowerMarkup, "tfoot")) {
dummyPlaceHolder.html(``);
return dummyPlaceHolder.querySelectorAll("table").get(0).childNodes.detach();
- } else if (startsWithTag(lowerMarkup, "tfoot")) {
- dummyPlaceHolder.html(``);
- return dummyPlaceHolder.querySelectorAll("table").get(2).childNodes.detach();
} else if (startsWithTag(lowerMarkup, "tr")) {
dummyPlaceHolder.html(``);
return dummyPlaceHolder.querySelectorAll("tbody").get(0).childNodes.detach();
- } else if (startsWithTag(lowerMarkup, "td")) {
+ } else if (startsWithTag(lowerMarkup, "td") || startsWithTag(lowerMarkup, "th")) {
dummyPlaceHolder.html(``);
return dummyPlaceHolder.querySelectorAll("tr").get(0).childNodes.detach();
}
@@ -981,15 +978,15 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl
}
firstElem(func: (item: Element, cnt?: number) => any = item => item): DomQuery {
- if (this.rootNode.length > 1) {
+ if (this.rootNode.length > 0) {
func(this.rootNode[0], 0);
}
return this;
}
lastElem(func: (item: Element, cnt?: number) => any = item => item): DomQuery {
- if (this.rootNode.length > 1) {
- func(this.rootNode[this.rootNode.length - 1], 0);
+ if (this.rootNode.length > 0) {
+ func(this.rootNode[this.rootNode.length - 1], this.rootNode.length - 1);
}
return this;
}
@@ -1922,7 +1919,7 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl
ctrl?.focus ? ctrl?.focus() : null;
// the selection range is our caret position
- ctrl?.setSelectiongRange ? ctrl?.setSelectiongRange(pos, pos) : null;
+ ctrl?.setSelectionRange ? ctrl?.setSelectionRange(pos, pos) : null;
}
/**
diff --git a/api/src/client/typescript/mona_dish/Lang.ts b/api/src/client/typescript/mona_dish/Lang.ts
index bbc9c2bc0b..9329f36ce2 100644
--- a/api/src/client/typescript/mona_dish/Lang.ts
+++ b/api/src/client/typescript/mona_dish/Lang.ts
@@ -161,8 +161,6 @@ export namespace Lang {
return it instanceof Function || typeof it === "function";
}
- // code from https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
- // license https://creativecommons.org/licenses/by-sa/2.5/
export function objAssign(target: any, ...theArgs: any[]) { // .length of function is 2
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
@@ -176,9 +174,11 @@ export namespace Lang {
theArgs.filter(item => item != null).forEach(item => {
let nextSource = item;
- Object.keys(nextSource)
- .filter(nextKey => Object.prototype.hasOwnProperty.call(nextSource, nextKey))
- .forEach(nextKey => to[nextKey] = nextSource[nextKey]);
+ const stringKeys = Object.keys(nextSource);
+ const symbolKeys = Object.getOwnPropertySymbols(nextSource)
+ .filter(sym => Object.prototype.propertyIsEnumerable.call(nextSource, sym));
+ [...stringKeys, ...symbolKeys]
+ .forEach(key => to[key] = nextSource[key]);
});
return to;
}
diff --git a/api/src/client/typescript/node_build/copysources.ts b/api/src/client/typescript/node_build/copysources.ts
index 31e1da82c4..2e7ef7ac54 100644
--- a/api/src/client/typescript/node_build/copysources.ts
+++ b/api/src/client/typescript/node_build/copysources.ts
@@ -55,5 +55,6 @@ copyFilesRecursively('./node_modules/jsf.js_next_gen/src/main/typescript/myfaces
copyFilesRecursively('./node_modules/jsf.js_next_gen/src/main/typescript/test', './typescript/faces/test');
copyFilesRecursively('./node_modules/jsf.js_next_gen/src/main/typescript/@types', './typescript/faces/@types');
copyFilesRecursively('./node_modules/mona-dish/src/main/typescript', './typescript/mona_dish');
+copyFilesRecursively('./node_modules/jsf.js_next_gen/dist/window/faces.d.ts', './typescript/faces/@types/definitions/faces.d.ts');
diff --git a/api/src/client/webpack.config.ts b/api/src/client/webpack.config.ts
index 4b49793285..1592104df9 100644
--- a/api/src/client/webpack.config.ts
+++ b/api/src/client/webpack.config.ts
@@ -18,6 +18,7 @@ import * as webpack from 'webpack';
import * as path from 'path'
let CompressionPlugin = require('compression-webpack-plugin');
+let CopyPlugin = require('copy-webpack-plugin');
/**
* we need to define the export in a function
@@ -53,6 +54,16 @@ function build(env: {[key:string]: string}, argv: {[key:string]: string}) {
"rxjs": "RxJS"
},
+ plugins: [
+ new CopyPlugin({
+ patterns: [
+ {
+ from: path.resolve(__dirname, './node_modules/jsf.js_next_gen/dist/window/faces.d.ts'),
+ to: path.resolve(__dirname, '../../target/classes/META-INF/resources/jakarta.faces/faces.d.ts')
+ }
+ ]
+ })
+ ],
module: {
rules: [
// all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'