iobroker.ipp 0.0.0-stage → 0.0.2

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Voodoo2man <Voodoo2man@outlook.de>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,81 @@
1
- # Temporary Holding Version
1
+ # ioBroker Internet Printing Protocol
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ![Network printer monitoring illustration](admin/ipp-banner.jpg)
4
+
5
+ [![NPM version](https://img.shields.io/npm/v/iobroker.ipp.svg)](https://www.npmjs.com/package/iobroker.ipp)
6
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.ipp.svg)](https://www.npmjs.com/package/iobroker.ipp)
7
+
8
+
9
+ ## ioBroker Internet Printing Protocol Adapter
10
+
11
+ The adapter polls configured network printers using the Internet Printing Protocol (IPP) and publishes read-only availability, printer state, toner or ink levels, and supported page counters. It uses `Get-Printer-Attributes` over IPP/2.0; device and firmware support varies, so optional attributes may not be reported by every printer. Compatibility depends on each printer’s IPP implementation; no particular device has been verified against this implementation.
12
+
13
+ Node.js 22 or newer, Admin 8.2.0 or newer, and js-controller 7.2.2 or newer are required.
14
+
15
+ ## Configuration
16
+
17
+ Add one or more printers in the adapter settings. Each row contains:
18
+
19
+ Printer entries are shown in individually collapsible sections, with fields arranged responsively for narrow and wide screens.
20
+
21
+ | Setting | Description |
22
+ | --- | --- |
23
+ | ID | Stable object-tree identifier; use 1-32 letters, digits, underscores, or hyphens. German umlauts are transliterated (`ä` to `ae`, `ö` to `oe`, `ü` to `ue`, `ß` to `ss`). |
24
+ | Label | Display name for the printer. |
25
+ | Host | Printer hostname or IP address. |
26
+ | Port | IPP port; defaults to `631`. |
27
+ | IPP resource path | Printer resource path; defaults to `/ipp/print`. |
28
+ | Use TLS | Use IPPS over TLS. |
29
+ | Verify TLS certificate | Validate the printer's TLS certificate; disabled by default for self-signed printer certificates. |
30
+
31
+ The polling interval defaults to 60 seconds and is limited to 10 seconds through 24 hours. The request timeout defaults to 5000 ms and is limited to 500-30000 ms. The adapter only sends the read-only `Get-Printer-Attributes` operation; it does not submit print jobs or change printer settings. Configure only printers that expose an IPP endpoint at the selected host, port, and resource path.
32
+
33
+ Invalid printer rows are skipped with a warning that identifies the invalid fields. Debug logging reports configured field names and value types without printing configured values.
34
+
35
+ When a printer is switched off or temporarily unreachable, the adapter marks it offline without a warning-level log; connection and timeout details are available at debug level. IPP protocol, TLS, and configuration errors remain warnings.
36
+
37
+ ## States
38
+
39
+ Each configured printer gets a device with `status`, `supplies`, `counters`, and `details` channels. Supplementary IPP attributes are created directly under `details` (for example, `details.printer_location`); attributes already represented by the canonical status, model, and counter states are not duplicated. The adapter requests all IPP attributes and creates states for returned values except explicitly excluded capabilities, verbose attribute families, and raw marker values; selected capability/default attributes are retained. Arrays and structured values are stored as JSON strings; scalar values keep their boolean, number, or string type. Unknown, non-excluded attributes are added automatically under `details`. `status.online` is always present; `status.model`, `status.message`, and numeric `status.state` are published when supported. The state value is `0` (`off`) while the printer is unreachable, and returns to the reported IPP state when it reconnects. Previously read measurements remain available while offline. IPP marker levels are normalized to percentages using the reported low/high bounds and rounded to two decimal places. `pages_completed`, `impressions_completed`, and `media_sheets_completed` are created only when the printer reports them. Missing values are cleared rather than replaced with zero during successful polls. Request details are not logged.
40
+
41
+ For supply reporting, the adapter combines `marker-names` with the corresponding entries in `marker-levels`, `marker-low-levels`, and `marker-high-levels`; `marker-types` is used when supplied. A supply is skipped if its name or any required numeric level/bound is missing, invalid, or out of range. Counters map from `pages-completed`, `impressions-completed`, and `media-sheets-completed` to `pages_completed`, `impressions_completed`, and `media_sheets_completed`. When reporting missing values, include the printer model and firmware, which of these IPP attributes the printer returns, and the raw values or state IDs that are absent.
42
+
43
+ [![Donate with PayPal](https://camo.githubusercontent.com/ef8c60305542390406855547e32850909812a73c4abada236de22a6dc88b313f/68747470733a2f2f7777772e70617970616c6f626a656374732e636f6d2f64655f44452f44452f692f62746e2f62746e5f646f6e61746543435f4c472e676966)](https://www.paypal.com/donate/?hosted_button_id=3RJQGSJNRSHUJ)
44
+
45
+ ## Changelog
46
+ <!--
47
+ Placeholder for the next version (at the beginning of the line):
48
+ ### **WORK IN PROGRESS**
49
+ -->
50
+ ### 0.0.2 (2026-10-28)
51
+
52
+ - (Voodoo2man) Added Node.js 22 support and documented printer supply and counter attributes.
53
+
54
+ ### 0.0.1 (2026-10-27)
55
+
56
+ - (Voodoo2man) initial release
57
+
58
+ Older release notes are available in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
59
+
60
+ ## License
61
+ MIT License
62
+
63
+ Copyright (c) 2026 Voodoo2man <Voodoo2man@outlook.de>
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining a copy
66
+ of this software and associated documentation files (the "Software"), to deal
67
+ in the Software without restriction, including without limitation the rights
68
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69
+ copies of the Software, and to permit persons to whom the Software is
70
+ furnished to do so, subject to the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be included in all
73
+ copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
81
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Adaptereinstellungen für ipp",
3
+ "Printers": "Drucker",
4
+ "ID": "ID",
5
+ "Label": "Bezeichnung",
6
+ "Host": "Host",
7
+ "Port": "Port",
8
+ "IPP resource path": "IPP-Ressourcenpfad",
9
+ "Use TLS": "TLS verwenden",
10
+ "Verify TLS certificate": "TLS-Zertifikat prüfen",
11
+ "Polling interval (seconds)": "Abfrageintervall (Sekunden)",
12
+ "Request timeout (milliseconds)": "Zeitlimit für Anfragen (Millisekunden)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Adapter settings for ipp",
3
+ "Printers": "Printers",
4
+ "ID": "ID",
5
+ "Label": "Label",
6
+ "Host": "Host",
7
+ "Port": "Port",
8
+ "IPP resource path": "IPP resource path",
9
+ "Use TLS": "Use TLS",
10
+ "Verify TLS certificate": "Verify TLS certificate",
11
+ "Polling interval (seconds)": "Polling interval (seconds)",
12
+ "Request timeout (milliseconds)": "Request timeout (milliseconds)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Configuración del adaptador ipp",
3
+ "Printers": "Impresoras",
4
+ "ID": "ID",
5
+ "Label": "Nombre",
6
+ "Host": "Host",
7
+ "Port": "Puerto",
8
+ "IPP resource path": "Ruta del recurso IPP",
9
+ "Use TLS": "Usar TLS",
10
+ "Verify TLS certificate": "Verificar certificado TLS",
11
+ "Polling interval (seconds)": "Intervalo de consulta (segundos)",
12
+ "Request timeout (milliseconds)": "Tiempo límite de solicitud (milisegundos)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Paramètres de l'adaptateur ipp",
3
+ "Printers": "Imprimantes",
4
+ "ID": "ID",
5
+ "Label": "Nom",
6
+ "Host": "Hôte",
7
+ "Port": "Port",
8
+ "IPP resource path": "Chemin de ressource IPP",
9
+ "Use TLS": "Utiliser TLS",
10
+ "Verify TLS certificate": "Vérifier le certificat TLS",
11
+ "Polling interval (seconds)": "Intervalle d'interrogation (secondes)",
12
+ "Request timeout (milliseconds)": "Délai d'attente de requête (millisecondes)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Impostazioni dell'adattatore ipp",
3
+ "Printers": "Stampanti",
4
+ "ID": "ID",
5
+ "Label": "Nome",
6
+ "Host": "Host",
7
+ "Port": "Porta",
8
+ "IPP resource path": "Percorso risorsa IPP",
9
+ "Use TLS": "Usa TLS",
10
+ "Verify TLS certificate": "Verifica certificato TLS",
11
+ "Polling interval (seconds)": "Intervallo di polling (secondi)",
12
+ "Request timeout (milliseconds)": "Timeout della richiesta (millisecondi)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Adapterinstellingen voor ipp",
3
+ "Printers": "Printers",
4
+ "ID": "ID",
5
+ "Label": "Naam",
6
+ "Host": "Host",
7
+ "Port": "Poort",
8
+ "IPP resource path": "IPP-resourcepad",
9
+ "Use TLS": "TLS gebruiken",
10
+ "Verify TLS certificate": "TLS-certificaat verifiëren",
11
+ "Polling interval (seconds)": "Pollinterval (seconden)",
12
+ "Request timeout (milliseconds)": "Time-out voor verzoeken (milliseconden)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Ustawienia adaptera ipp",
3
+ "Printers": "Drukarki",
4
+ "ID": "ID",
5
+ "Label": "Nazwa",
6
+ "Host": "Host",
7
+ "Port": "Port",
8
+ "IPP resource path": "Ścieżka zasobu IPP",
9
+ "Use TLS": "Użyj TLS",
10
+ "Verify TLS certificate": "Weryfikuj certyfikat TLS",
11
+ "Polling interval (seconds)": "Interwał odpytywania (sekundy)",
12
+ "Request timeout (milliseconds)": "Limit czasu żądania (milisekundy)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Configurações do adaptador ipp",
3
+ "Printers": "Impressoras",
4
+ "ID": "ID",
5
+ "Label": "Nome",
6
+ "Host": "Host",
7
+ "Port": "Porta",
8
+ "IPP resource path": "Caminho do recurso IPP",
9
+ "Use TLS": "Usar TLS",
10
+ "Verify TLS certificate": "Verificar certificado TLS",
11
+ "Polling interval (seconds)": "Intervalo de consulta (segundos)",
12
+ "Request timeout (milliseconds)": "Tempo limite da solicitação (milissegundos)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Настройки адаптера ipp",
3
+ "Printers": "Принтеры",
4
+ "ID": "Идентификатор",
5
+ "Label": "Название",
6
+ "Host": "Узел",
7
+ "Port": "Порт",
8
+ "IPP resource path": "Путь ресурса IPP",
9
+ "Use TLS": "Использовать TLS",
10
+ "Verify TLS certificate": "Проверять сертификат TLS",
11
+ "Polling interval (seconds)": "Интервал опроса (секунды)",
12
+ "Request timeout (milliseconds)": "Тайм-аут запроса (миллисекунды)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "Налаштування адаптера ipp",
3
+ "Printers": "Принтери",
4
+ "ID": "Ідентифікатор",
5
+ "Label": "Назва",
6
+ "Host": "Хост",
7
+ "Port": "Порт",
8
+ "IPP resource path": "Шлях ресурсу IPP",
9
+ "Use TLS": "Використовувати TLS",
10
+ "Verify TLS certificate": "Перевіряти сертифікат TLS",
11
+ "Polling interval (seconds)": "Інтервал опитування (секунди)",
12
+ "Request timeout (milliseconds)": "Час очікування запиту (мілісекунди)"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "ipp adapter settings": "ipp 适配器设置",
3
+ "Printers": "打印机",
4
+ "ID": "ID",
5
+ "Label": "名称",
6
+ "Host": "主机",
7
+ "Port": "端口",
8
+ "IPP resource path": "IPP 资源路径",
9
+ "Use TLS": "使用 TLS",
10
+ "Verify TLS certificate": "验证 TLS 证书",
11
+ "Polling interval (seconds)": "轮询间隔(秒)",
12
+ "Request timeout (milliseconds)": "请求超时(毫秒)"
13
+ }
Binary file
package/admin/ipp.png ADDED
Binary file
@@ -0,0 +1,51 @@
1
+ {
2
+ "i18n": true,
3
+ "type": "panel",
4
+ "items": {
5
+ "printers": {
6
+ "type": "accordion",
7
+ "label": "Printers",
8
+ "titleAttr": "id",
9
+ "items": [
10
+ { "attr": "id", "type": "text", "label": "ID", "xs": 12, "sm": 6 },
11
+ { "attr": "label", "type": "text", "label": "Label", "xs": 12, "sm": 6 },
12
+ { "attr": "host", "type": "text", "label": "Host", "xs": 12, "md": 8 },
13
+ { "attr": "port", "type": "number", "label": "Port", "default": 631, "xs": 12, "md": 4 },
14
+ { "attr": "basePath", "type": "text", "label": "IPP resource path", "default": "/ipp/print", "xs": 12 },
15
+ { "attr": "ssl", "type": "checkbox", "label": "Use TLS", "xs": 12, "sm": 6 },
16
+ { "attr": "verifySsl", "type": "checkbox", "label": "Verify TLS certificate", "default": false, "xs": 12, "sm": 6 }
17
+ ],
18
+ "xs": 12,
19
+ "sm": 12,
20
+ "md": 12,
21
+ "lg": 12,
22
+ "xl": 12
23
+ },
24
+ "intervalSeconds": {
25
+ "type": "number",
26
+ "label": "Polling interval (seconds)",
27
+ "default": 60,
28
+ "min": 10,
29
+ "max": 86400,
30
+ "step": 1,
31
+ "xs": 12,
32
+ "sm": 6,
33
+ "md": 6,
34
+ "lg": 6,
35
+ "xl": 6
36
+ },
37
+ "timeoutMs": {
38
+ "type": "number",
39
+ "label": "Request timeout (milliseconds)",
40
+ "default": 5000,
41
+ "min": 500,
42
+ "max": 30000,
43
+ "step": 500,
44
+ "xs": 12,
45
+ "sm": 6,
46
+ "md": 6,
47
+ "lg": 6,
48
+ "xl": 6
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "common": {
3
+ "name": "ipp",
4
+ "nogit": true,
5
+ "version": "0.0.2",
6
+ "news": {
7
+ "0.0.2": {
8
+ "en": "Added Node.js 22 support and documented printer supply and counter attributes.",
9
+ "de": "Support für Node.js 22 hinzugefügt und Druckerbedarf und Zähler dokumentiert.",
10
+ "ru": "Добавлена поддержка Node.js 22 и документированные атрибуты расходных материалов и счетчиков принтеров.",
11
+ "pt": "Foi adicionado o suporte ao Node.js 22 e documentou a fonte e o contador.",
12
+ "nl": "Ondersteuning voor Node.js 22 toegevoegd en de printerbenodigdheden en tellerkenmerken gedocumenteerd.",
13
+ "fr": "Ajout du support de Node.js 22 et des attributs d'imprimante et de compteur documentés.",
14
+ "it": "Aggiunto il supporto per Node.js 22 e attributi di stampa e contatore.",
15
+ "es": "Se ha añadido Node.js 22 y ha documentado los atributos de suministro y contador.",
16
+ "pl": "Dodano obsługę Node.js 22 i udokumentowane atrybuty zasilania drukarki i licznika.",
17
+ "uk": "Додана підтримка Node.js 22 і задокументована подача принтера і атрибути лічильника.",
18
+ "zh-cn": "添加了 Node.js 22 支持,并记录了打印机供应和计数器属性。"
19
+ },
20
+ "0.0.1": {
21
+ "en": "initial release",
22
+ "de": "Erstveröffentlichung",
23
+ "ru": "Начальная версия",
24
+ "pt": "lançamento inicial",
25
+ "nl": "Eerste uitgave",
26
+ "fr": "Première version",
27
+ "it": "Versione iniziale",
28
+ "es": "Versión inicial",
29
+ "pl": "Pierwsze wydanie",
30
+ "uk": "Початкова версія",
31
+ "zh-cn": "首次出版"
32
+ }
33
+ },
34
+ "titleLang": {
35
+ "en": "Internet Printing Protocol",
36
+ "de": "Internet Printing Protocol",
37
+ "ru": "Internet Printing Protocol",
38
+ "pt": "Internet Printing Protocol",
39
+ "nl": "Internet Printing Protocol",
40
+ "fr": "Internet Printing Protocol",
41
+ "it": "Internet Printing Protocol",
42
+ "es": "Internet Printing Protocol",
43
+ "pl": "Internet Printing Protocol",
44
+ "uk": "Internet Printing Protocol",
45
+ "zh-cn": "Internet Printing Protocol"
46
+ },
47
+ "desc": {
48
+ "en": "Read-only monitoring of configured network printers using IPP/2.0 over HTTP or HTTPS",
49
+ "de": "Schreibgeschützte Überwachung konfigurierter Netzwerkdrucker über IPP/2.0 mit HTTP oder HTTPS",
50
+ "ru": "Мониторинг настроенных сетевых принтеров через IPP/2.0 по HTTP или HTTPS только для чтения",
51
+ "pt": "Monitorização de impressoras de rede configuradas através de IPP/2.0 por HTTP ou HTTPS, apenas para leitura",
52
+ "nl": "Alleen-lezen bewaking van geconfigureerde netwerkprinters via IPP/2.0 over HTTP of HTTPS",
53
+ "fr": "Surveillance en lecture seule des imprimantes réseau configurées via IPP/2.0 en HTTP ou HTTPS",
54
+ "it": "Monitoraggio in sola lettura delle stampanti di rete configurate tramite IPP/2.0 su HTTP o HTTPS",
55
+ "es": "Supervisión de solo lectura de impresoras de red mediante IPP/2.0 por HTTP o HTTPS",
56
+ "pl": "Monitorowanie skonfigurowanych drukarek sieciowych przez IPP/2.0 za pomocą HTTP lub HTTPS tylko do odczytu",
57
+ "uk": "Моніторинг налаштованих мережевих принтерів через IPP/2.0 за HTTP або HTTPS лише для читання",
58
+ "zh-cn": "通过 HTTP 或 HTTPS 上的 IPP/2.0 只读监控已配置的网络打印机"
59
+ },
60
+ "authors": [
61
+ "Voodoo2man <Voodoo2man@outlook.de>"
62
+ ],
63
+ "keywords": [
64
+ "printer",
65
+ "printing",
66
+ "IPP",
67
+ "IPP/2.0",
68
+ "IPPS"
69
+ ],
70
+ "licenseInformation": {
71
+ "type": "free",
72
+ "license": "MIT"
73
+ },
74
+ "platform": "Javascript/Node.js",
75
+ "icon": "ipp.png",
76
+ "enabled": true,
77
+ "extIcon": "https://raw.githubusercontent.com/Voodoo2man/ioBroker.ipp/main/admin/ipp.png",
78
+ "readme": "https://github.com/Voodoo2man/ioBroker.ipp/blob/main/README.md",
79
+ "loglevel": "info",
80
+ "tier": 3,
81
+ "mode": "daemon",
82
+ "type": "network",
83
+ "compact": true,
84
+ "connectionType": "local",
85
+ "dataSource": "poll",
86
+ "adminUI": {
87
+ "config": "json"
88
+ },
89
+ "dependencies": [
90
+ {
91
+ "js-controller": ">=7.2.2"
92
+ }
93
+ ],
94
+ "globalDependencies": [
95
+ {
96
+ "admin": ">=8.2.0"
97
+ }
98
+ ]
99
+ },
100
+ "native": {
101
+ "printers": [],
102
+ "intervalSeconds": 60,
103
+ "timeoutMs": 5000
104
+ },
105
+ "objects": [],
106
+ "instanceObjects": []
107
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePrinterConfig = parsePrinterConfig;
4
+ exports.getPrinterConfigErrors = getPrinterConfigErrors;
5
+ exports.describePrinterConfig = describePrinterConfig;
6
+ function normalizeCheckbox(value) {
7
+ if (value === undefined || value === null || value === '') {
8
+ return false;
9
+ }
10
+ if (value === true || value === 1 || value === '1' || value === 'true') {
11
+ return true;
12
+ }
13
+ if (value === false || value === 0 || value === '0' || value === 'false') {
14
+ return false;
15
+ }
16
+ return null;
17
+ }
18
+ function normalizePrinterId(value) {
19
+ return value
20
+ .replace(/[äÄ]/g, match => (match === 'Ä' ? 'Ae' : 'ae'))
21
+ .replace(/[öÖ]/g, match => (match === 'Ö' ? 'Oe' : 'oe'))
22
+ .replace(/[üÜ]/g, match => (match === 'Ü' ? 'Ue' : 'ue'))
23
+ .replace(/ß/g, 'ss');
24
+ }
25
+ function parsePrinterConfig(value) {
26
+ const errors = getPrinterConfigErrors(value);
27
+ if (!value || typeof value !== 'object' || Array.isArray(value) || errors.length > 0) {
28
+ return { errors };
29
+ }
30
+ const printer = value;
31
+ const ssl = normalizeCheckbox(printer.ssl);
32
+ const verifySsl = normalizeCheckbox(printer.verifySsl);
33
+ if (ssl === null || verifySsl === null) {
34
+ if (ssl === null) {
35
+ errors.push('ssl must be true/false, 1/0, or an unchecked checkbox');
36
+ }
37
+ if (verifySsl === null) {
38
+ errors.push('verifySsl must be true/false, 1/0, or an unchecked checkbox');
39
+ }
40
+ return { errors };
41
+ }
42
+ return {
43
+ config: {
44
+ id: normalizePrinterId(printer.id),
45
+ label: typeof printer.label === 'string' ? printer.label : undefined,
46
+ host: printer.host,
47
+ port: printer.port,
48
+ basePath: printer.basePath,
49
+ ssl,
50
+ verifySsl,
51
+ },
52
+ errors: [],
53
+ };
54
+ }
55
+ function getPrinterConfigErrors(value) {
56
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
57
+ return ['row must be an object'];
58
+ }
59
+ const printer = value;
60
+ const errors = [];
61
+ if (typeof printer.id !== 'string' || !/^[A-Za-z0-9_-]{1,32}$/.test(normalizePrinterId(printer.id))) {
62
+ errors.push('id must contain 1-32 letters, digits, underscores, or hyphens');
63
+ }
64
+ if (typeof printer.host !== 'string' || printer.host.trim() === '') {
65
+ errors.push('host is missing or is not text');
66
+ }
67
+ if (printer.port !== undefined &&
68
+ printer.port !== '' &&
69
+ (!Number.isInteger(Number(printer.port)) || Number(printer.port) < 1 || Number(printer.port) > 65535)) {
70
+ errors.push('port must be an integer from 1 to 65535');
71
+ }
72
+ if (printer.basePath !== undefined &&
73
+ (typeof printer.basePath !== 'string' || !printer.basePath.startsWith('/') || printer.basePath.includes('..'))) {
74
+ errors.push('basePath must start with / and must not contain ..');
75
+ }
76
+ for (const key of ['ssl', 'verifySsl']) {
77
+ if (normalizeCheckbox(printer[key]) === null) {
78
+ errors.push(`${key} must be true/false, 1/0, or an unchecked checkbox`);
79
+ }
80
+ }
81
+ return errors;
82
+ }
83
+ function describePrinterConfig(value) {
84
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
85
+ return `rowType=${Array.isArray(value) ? 'array' : typeof value}`;
86
+ }
87
+ const printer = value;
88
+ const fields = ['id', 'label', 'host', 'port', 'basePath', 'ssl', 'verifySsl'];
89
+ return fields.map(key => `${key}=${key in printer ? typeof printer[key] : 'missing'}`).join(', ');
90
+ }