node-red-contrib-knx-ultimate 6.0.4 → 6.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/nodes/commonFunctions.js +33 -0
  4. package/nodes/icons/node-matter-bridge-icon.svg +6 -0
  5. package/nodes/icons/node-matter-controller-icon.svg +6 -0
  6. package/nodes/knxUltimate-config.js +5 -1
  7. package/nodes/knxUltimateMatterBridge.html +39 -21
  8. package/nodes/knxUltimateMatterBridge.js +70 -15
  9. package/nodes/knxUltimateMatterControllerDevice.html +2 -7
  10. package/nodes/locales/de/knxUltimateMatterBridge.html +4 -2
  11. package/nodes/locales/de/knxUltimateMatterBridge.json +13 -10
  12. package/nodes/locales/de/matter-config.html +4 -0
  13. package/nodes/locales/de/matter-config.json +7 -2
  14. package/nodes/locales/de/matterbridge-config.html +4 -0
  15. package/nodes/locales/de/matterbridge-config.json +4 -1
  16. package/nodes/locales/en/knxUltimateMatterBridge.html +4 -2
  17. package/nodes/locales/en/knxUltimateMatterBridge.json +13 -10
  18. package/nodes/locales/en/matter-config.html +4 -0
  19. package/nodes/locales/en/matter-config.json +7 -2
  20. package/nodes/locales/en/matterbridge-config.html +4 -0
  21. package/nodes/locales/en/matterbridge-config.json +4 -1
  22. package/nodes/locales/es/knxUltimateMatterBridge.html +4 -2
  23. package/nodes/locales/es/knxUltimateMatterBridge.json +13 -10
  24. package/nodes/locales/es/matter-config.html +4 -0
  25. package/nodes/locales/es/matter-config.json +7 -2
  26. package/nodes/locales/es/matterbridge-config.html +4 -0
  27. package/nodes/locales/es/matterbridge-config.json +4 -1
  28. package/nodes/locales/fr/knxUltimateMatterBridge.html +4 -2
  29. package/nodes/locales/fr/knxUltimateMatterBridge.json +13 -10
  30. package/nodes/locales/fr/matter-config.html +4 -0
  31. package/nodes/locales/fr/matter-config.json +7 -2
  32. package/nodes/locales/fr/matterbridge-config.html +4 -0
  33. package/nodes/locales/fr/matterbridge-config.json +4 -1
  34. package/nodes/locales/it/knxUltimateMatterBridge.html +4 -2
  35. package/nodes/locales/it/knxUltimateMatterBridge.json +13 -10
  36. package/nodes/locales/it/matter-config.html +4 -0
  37. package/nodes/locales/it/matter-config.json +7 -2
  38. package/nodes/locales/it/matterbridge-config.html +4 -0
  39. package/nodes/locales/it/matterbridge-config.json +4 -1
  40. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +4 -2
  41. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +13 -10
  42. package/nodes/locales/zh-CN/matter-config.html +4 -0
  43. package/nodes/locales/zh-CN/matter-config.json +7 -2
  44. package/nodes/locales/zh-CN/matterbridge-config.html +4 -0
  45. package/nodes/locales/zh-CN/matterbridge-config.json +4 -1
  46. package/nodes/matter-config.html +59 -18
  47. package/nodes/matter-config.js +33 -0
  48. package/nodes/matterbridge-config.html +73 -3
  49. package/nodes/matterbridge-config.js +38 -0
  50. package/nodes/utils/matterBridgeDeviceFactory.mjs +244 -130
  51. package/nodes/utils/matterBridgeEngine.mjs +27 -22
  52. package/nodes/utils/matterEngine.mjs +13 -7
  53. package/nodes/utils/matterEnvironmentLock.mjs +32 -0
  54. package/nodes/utils/matterStorageBackup.js +100 -0
  55. package/package.json +1 -1
  56. package/resources/htmlUtils.js +11 -0
@@ -0,0 +1,100 @@
1
+ const fs = require('fs/promises')
2
+ const path = require('path')
3
+
4
+ const BACKUP_FORMAT = 'knx-ultimate-matter-storage'
5
+ const BACKUP_VERSION = 1
6
+ const MAX_BACKUP_BYTES = 64 * 1024 * 1024
7
+
8
+ const instancePath = (storagePath, instanceId) => path.join(storagePath, instanceId)
9
+
10
+ const listFiles = async (root, current = root) => {
11
+ const entries = await fs.readdir(current, { withFileTypes: true })
12
+ const files = []
13
+ for (const entry of entries) {
14
+ if (entry.isSymbolicLink()) throw new Error('Matter storage contains an unsupported symbolic link')
15
+ const absolute = path.join(current, entry.name)
16
+ if (entry.isDirectory()) files.push(...await listFiles(root, absolute))
17
+ else if (entry.isFile()) files.push(path.relative(root, absolute).split(path.sep).join('/'))
18
+ }
19
+ return files.sort()
20
+ }
21
+
22
+ const exportMatterStorage = async ({ storagePath, instanceId, kind, protocolVersion, packageVersion }) => {
23
+ const root = instancePath(storagePath, instanceId)
24
+ let names
25
+ try {
26
+ names = await listFiles(root)
27
+ } catch (error) {
28
+ if (error.code === 'ENOENT') names = []
29
+ else throw error
30
+ }
31
+ const files = []
32
+ let totalBytes = 0
33
+ for (const name of names) {
34
+ const content = await fs.readFile(path.join(root, ...name.split('/')))
35
+ totalBytes += content.length
36
+ if (totalBytes > MAX_BACKUP_BYTES) throw new Error('Matter backup exceeds the 64 MB safety limit')
37
+ files.push({ path: name, content: content.toString('base64') })
38
+ }
39
+ return {
40
+ format: BACKUP_FORMAT,
41
+ version: BACKUP_VERSION,
42
+ kind,
43
+ createdAt: new Date().toISOString(),
44
+ sourceInstanceId: instanceId,
45
+ matterProtocolVersion: protocolVersion,
46
+ matterPackageVersion: packageVersion,
47
+ files
48
+ }
49
+ }
50
+
51
+ const validateRelativePath = (name) => {
52
+ if (typeof name !== 'string' || name === '' || name.includes('\\')) return false
53
+ const normalized = path.posix.normalize(name)
54
+ return normalized === name && !path.posix.isAbsolute(name) && name !== '..' && !name.startsWith('../')
55
+ }
56
+
57
+ const importMatterStorage = async ({ storagePath, instanceId, kind, backup }) => {
58
+ if (!backup || backup.format !== BACKUP_FORMAT || backup.version !== BACKUP_VERSION) throw new Error('Unsupported Matter backup file')
59
+ if (backup.kind !== kind) throw new Error(`This is a Matter ${backup.kind || 'unknown'} backup, not a ${kind} backup`)
60
+ if (!Array.isArray(backup.files)) throw new Error('Invalid Matter backup: files are missing')
61
+
62
+ const decoded = []
63
+ const seen = new Set()
64
+ let totalBytes = 0
65
+ for (const file of backup.files) {
66
+ if (!file || !validateRelativePath(file.path) || seen.has(file.path) || typeof file.content !== 'string') throw new Error('Invalid Matter backup file entry')
67
+ const content = Buffer.from(file.content, 'base64')
68
+ totalBytes += content.length
69
+ if (totalBytes > MAX_BACKUP_BYTES) throw new Error('Matter backup exceeds the 64 MB safety limit')
70
+ seen.add(file.path)
71
+ decoded.push({ path: file.path, content })
72
+ }
73
+
74
+ await fs.mkdir(storagePath, { recursive: true })
75
+ const target = instancePath(storagePath, instanceId)
76
+ const temporary = `${target}.import-${process.pid}-${Date.now()}`
77
+ const previous = `${target}.previous-${process.pid}-${Date.now()}`
78
+ await fs.mkdir(temporary, { recursive: true })
79
+ try {
80
+ for (const file of decoded) {
81
+ const destination = path.join(temporary, ...file.path.split('/'))
82
+ await fs.mkdir(path.dirname(destination), { recursive: true })
83
+ await fs.writeFile(destination, file.content, { mode: 0o600 })
84
+ }
85
+ let hadTarget = true
86
+ try { await fs.rename(target, previous) } catch (error) { if (error.code === 'ENOENT') hadTarget = false; else throw error }
87
+ try {
88
+ await fs.rename(temporary, target)
89
+ } catch (error) {
90
+ if (hadTarget) await fs.rename(previous, target)
91
+ throw error
92
+ }
93
+ if (hadTarget) await fs.rm(previous, { recursive: true, force: true })
94
+ } catch (error) {
95
+ await fs.rm(temporary, { recursive: true, force: true })
96
+ throw error
97
+ }
98
+ }
99
+
100
+ module.exports = { exportMatterStorage, importMatterStorage, BACKUP_FORMAT, BACKUP_VERSION, MAX_BACKUP_BYTES }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "6.0.4",
6
+ "version": "6.0.6",
7
7
  "description": "Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control, ETS group address importer, KNX AI for diagnosticsand KNX routing between interfaces. Easy to use and highly configurable.",
8
8
  "files": [
9
9
  "nodes/",
@@ -1,6 +1,17 @@
1
1
  /* global window, document, MutationObserver, $, jQuery */
2
2
  /* eslint-disable camelcase */
3
3
 
4
+ // Keep informational tips aligned with the full width of our Node-RED editor forms.
5
+ // Node-RED applies a max-width to .form-tips by default, which otherwise leaves
6
+ // inconsistent empty space in wider edit dialogs.
7
+ ;(function ensureKnxUltimateEditorStyles () {
8
+ if (document.getElementById('knx-ultimate-editor-styles')) return
9
+ const style = document.createElement('style')
10
+ style.id = 'knx-ultimate-editor-styles'
11
+ style.textContent = '.form-tips { width: 100% !important; max-width: none !important; box-sizing: border-box !important; background-color: #e8f5e9 !important; }'
12
+ document.head.appendChild(style)
13
+ })()
14
+
4
15
  // 31/03/2020 Search Helper
5
16
  function htmlUtilsfullCSVSearch (sourceText, searchString) {
6
17
  let aSearchWords = []