node-red-contrib-senec-cloud-v2 0.2.0

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.
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication Manager
4
+ *
5
+ * Handles SENEC Cloud authentication, token management, and refresh logic.
6
+ *
7
+ * Since the SENEC API change (August 2025), authentication uses the
8
+ * SENEC Keycloak SSO with the OAuth2 Authorization Code + PKCE flow,
9
+ * emulating the official SENEC app:
10
+ *
11
+ * 1. GET /auth (login page, collects session cookies)
12
+ * 2. POST username to the login form
13
+ * 3. POST password to the second-step form
14
+ * 4. Receive redirect to senec-app-auth://keycloak.prod?code=...
15
+ * 5. Exchange authorization code for access + refresh tokens
16
+ *
17
+ * Access tokens are short-lived (~5 minutes); refresh tokens last ~30 days.
18
+ * Token refresh uses the standard OAuth2 refresh_token grant.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ var __importDefault = (this && this.__importDefault) || function (mod) {
54
+ return (mod && mod.__esModule) ? mod : { "default": mod };
55
+ };
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.AuthenticationManager = void 0;
58
+ const axios_1 = __importDefault(require("axios"));
59
+ const crypto = __importStar(require("crypto"));
60
+ const querystring = __importStar(require("querystring"));
61
+ const SSO_BASE = 'https://sso.senec.com/realms/senec/protocol/openid-connect';
62
+ const CLIENT_ID = 'endcustomer-app-frontend';
63
+ const REDIRECT_URI = 'senec-app-auth://keycloak.prod';
64
+ const OAUTH_SCOPE = 'roles meinsenec';
65
+ function base64url(buffer) {
66
+ return buffer
67
+ .toString('base64')
68
+ .replace(/\+/g, '-')
69
+ .replace(/\//g, '_')
70
+ .replace(/=+$/, '');
71
+ }
72
+ class AuthenticationManager {
73
+ constructor(credentials) {
74
+ this.token = null;
75
+ this.tokenExpiry = null;
76
+ this.refreshTokenValue = null;
77
+ this.refreshTokenExpiry = null;
78
+ this.credentials = credentials;
79
+ this.httpClient = axios_1.default.create({
80
+ timeout: 30000,
81
+ });
82
+ }
83
+ /**
84
+ * Authenticate with SENEC Cloud (OAuth2 Authorization Code + PKCE)
85
+ */
86
+ async authenticate() {
87
+ try {
88
+ const codeVerifier = base64url(crypto.randomBytes(64));
89
+ const codeChallenge = base64url(crypto.createHash('sha256').update(codeVerifier).digest());
90
+ // Cookie jar for the Keycloak login session
91
+ const cookies = {};
92
+ const storeCookies = (headers) => {
93
+ const setCookie = headers['set-cookie'] || [];
94
+ for (const c of setCookie) {
95
+ const [pair] = c.split(';');
96
+ const eq = pair.indexOf('=');
97
+ if (eq > 0) {
98
+ cookies[pair.substring(0, eq).trim()] = pair.substring(eq + 1);
99
+ }
100
+ }
101
+ };
102
+ const cookieHeader = () => Object.entries(cookies)
103
+ .map(([k, v]) => `${k}=${v}`)
104
+ .join('; ');
105
+ // Step 1: GET login page
106
+ const authUrl = `${SSO_BASE}/auth?` +
107
+ querystring.stringify({
108
+ client_id: CLIENT_ID,
109
+ redirect_uri: REDIRECT_URI,
110
+ response_type: 'code',
111
+ scope: OAUTH_SCOPE,
112
+ code_challenge: codeChallenge,
113
+ code_challenge_method: 'S256',
114
+ });
115
+ const loginPage = await this.httpClient.get(authUrl);
116
+ storeCookies(loginPage.headers);
117
+ const extractFormAction = (html) => {
118
+ const match = html.match(/action="([^"]+)"/);
119
+ return match ? match[1].replace(/&amp;/g, '&') : null;
120
+ };
121
+ const usernameFormAction = extractFormAction(loginPage.data);
122
+ if (!usernameFormAction) {
123
+ throw new Error('SENEC login page did not contain a login form');
124
+ }
125
+ const postForm = async (action, fields) => this.httpClient.post(action, querystring.stringify(fields), {
126
+ headers: {
127
+ 'Content-Type': 'application/x-www-form-urlencoded',
128
+ Cookie: cookieHeader(),
129
+ },
130
+ maxRedirects: 0,
131
+ validateStatus: (s) => s === 200 || s === 302,
132
+ });
133
+ // Step 2: submit username
134
+ let response = await postForm(usernameFormAction, {
135
+ username: this.credentials.username,
136
+ credentialId: '',
137
+ });
138
+ storeCookies(response.headers);
139
+ // Step 3: submit password (second form step)
140
+ if (response.status === 200) {
141
+ const passwordFormAction = extractFormAction(response.data);
142
+ if (!passwordFormAction) {
143
+ throw new Error('Invalid username (SENEC login rejected the account name)');
144
+ }
145
+ response = await postForm(passwordFormAction, {
146
+ password: this.credentials.password,
147
+ credentialId: '',
148
+ });
149
+ storeCookies(response.headers);
150
+ }
151
+ if (response.status !== 302) {
152
+ // Form re-rendered => wrong credentials
153
+ throw new Error('Invalid username or password (check your SENEC app credentials)');
154
+ }
155
+ // Step 4: extract authorization code from redirect
156
+ const location = response.headers.location || '';
157
+ const codeMatch = location.match(/[?&#]code=([^&]+)/);
158
+ if (!codeMatch) {
159
+ throw new Error('SENEC login did not return an authorization code');
160
+ }
161
+ // Step 5: exchange code for tokens
162
+ const tokenResponse = await this.httpClient.post(`${SSO_BASE}/token`, querystring.stringify({
163
+ grant_type: 'authorization_code',
164
+ client_id: CLIENT_ID,
165
+ redirect_uri: REDIRECT_URI,
166
+ code: codeMatch[1],
167
+ code_verifier: codeVerifier,
168
+ }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
169
+ this.storeTokens(tokenResponse.data);
170
+ if (!this.token || !this.tokenExpiry) {
171
+ throw new Error('Failed to obtain valid token from authentication response');
172
+ }
173
+ return {
174
+ token: this.token,
175
+ expiry: this.tokenExpiry,
176
+ };
177
+ }
178
+ catch (error) {
179
+ this.clearToken();
180
+ throw new Error(`Authentication failed: ${this.describeError(error)}`);
181
+ }
182
+ }
183
+ /**
184
+ * Refresh the authentication token using the OAuth2 refresh_token grant.
185
+ * Falls back to a full re-authentication if no valid refresh token exists
186
+ * or the refresh fails.
187
+ */
188
+ async refreshToken() {
189
+ const refreshUsable = this.refreshTokenValue &&
190
+ (!this.refreshTokenExpiry || Date.now() < this.refreshTokenExpiry.getTime() - 60000);
191
+ if (!refreshUsable) {
192
+ return this.authenticate();
193
+ }
194
+ try {
195
+ const response = await this.httpClient.post(`${SSO_BASE}/token`, querystring.stringify({
196
+ grant_type: 'refresh_token',
197
+ client_id: CLIENT_ID,
198
+ refresh_token: this.refreshTokenValue,
199
+ }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
200
+ this.storeTokens(response.data);
201
+ if (!this.token || !this.tokenExpiry) {
202
+ throw new Error('Failed to obtain valid token from refresh response');
203
+ }
204
+ return {
205
+ token: this.token,
206
+ expiry: this.tokenExpiry,
207
+ };
208
+ }
209
+ catch (error) {
210
+ // If refresh fails, try full re-authentication
211
+ return this.authenticate();
212
+ }
213
+ }
214
+ /**
215
+ * Check if the current token is valid
216
+ */
217
+ isTokenValid() {
218
+ if (!this.token || !this.tokenExpiry) {
219
+ return false;
220
+ }
221
+ // Consider token invalid if it expires in less than 30 seconds
222
+ // (SENEC access tokens are only valid for ~5 minutes)
223
+ const bufferTime = 30 * 1000;
224
+ return Date.now() < this.tokenExpiry.getTime() - bufferTime;
225
+ }
226
+ /**
227
+ * Whether a (probably valid) refresh token is available
228
+ */
229
+ canRefresh() {
230
+ return (!!this.refreshTokenValue &&
231
+ (!this.refreshTokenExpiry || Date.now() < this.refreshTokenExpiry.getTime() - 60000));
232
+ }
233
+ /**
234
+ * Get the current token
235
+ */
236
+ getToken() {
237
+ return this.token;
238
+ }
239
+ /**
240
+ * Clear the stored tokens
241
+ */
242
+ clearToken() {
243
+ this.token = null;
244
+ this.tokenExpiry = null;
245
+ this.refreshTokenValue = null;
246
+ this.refreshTokenExpiry = null;
247
+ }
248
+ /**
249
+ * Store tokens from a Keycloak token endpoint response
250
+ */
251
+ storeTokens(data) {
252
+ this.token = data.access_token || null;
253
+ const expiresIn = data.expires_in || 300;
254
+ this.tokenExpiry = new Date(Date.now() + expiresIn * 1000);
255
+ if (data.refresh_token) {
256
+ this.refreshTokenValue = data.refresh_token;
257
+ const refreshExpiresIn = data.refresh_expires_in || 2592000;
258
+ this.refreshTokenExpiry = new Date(Date.now() + refreshExpiresIn * 1000);
259
+ }
260
+ }
261
+ /**
262
+ * Build a user-friendly error description without leaking credentials
263
+ */
264
+ describeError(error) {
265
+ const axiosError = error;
266
+ if (axiosError && axiosError.isAxiosError) {
267
+ const status = axiosError.response?.status;
268
+ if (status === 401 || status === 403) {
269
+ return 'Invalid username or password (check your SENEC app credentials)';
270
+ }
271
+ if (status === 429) {
272
+ return 'Too many login attempts - rate limited by SENEC Cloud, please wait';
273
+ }
274
+ if (status) {
275
+ return `SENEC Cloud returned HTTP ${status}`;
276
+ }
277
+ if (axiosError.code === 'ECONNABORTED' || axiosError.code === 'ETIMEDOUT') {
278
+ return 'Connection timeout while contacting SENEC Cloud';
279
+ }
280
+ if (axiosError.code === 'ENOTFOUND' || axiosError.code === 'ECONNREFUSED') {
281
+ return 'Cannot reach SENEC Cloud - check your internet connection';
282
+ }
283
+ }
284
+ return error instanceof Error ? error.message : 'Unknown error';
285
+ }
286
+ }
287
+ exports.AuthenticationManager = AuthenticationManager;
288
+ //# sourceMappingURL=auth-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-manager.js","sourceRoot":"","sources":["../../src/lib/auth-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Data Parser
3
+ *
4
+ * Parses SENEC Cloud App API dashboard responses (v2 format from
5
+ * app-gateway.prod.senec.dev) and transforms them into the expected
6
+ * payload structure for backward compatibility.
7
+ */
8
+ import { SenecData, ValidationResult } from '../types/senec';
9
+ export declare class DataParser {
10
+ private debugMode;
11
+ constructor(debugMode?: boolean);
12
+ /**
13
+ * Parse SENEC Cloud App API v2 dashboard response into structured data.
14
+ *
15
+ * Expected input structure (v2/senec/systems/{id}/dashboard):
16
+ * {
17
+ * currently: {
18
+ * powerGenerationInW, powerConsumptionInW,
19
+ * gridFeedInInW, gridDrawInW,
20
+ * batteryChargeInW, batteryDischargeInW,
21
+ * batteryLevelInPercent, selfSufficiencyInPercent, wallboxInW
22
+ * },
23
+ * today: {
24
+ * powerGenerationInWh, powerConsumptionInWh,
25
+ * gridFeedInInWh, gridDrawInWh, ...
26
+ * },
27
+ * timestamp: ISO string
28
+ * }
29
+ *
30
+ * @param apiResponse - Raw dashboard API response from SENEC Cloud
31
+ * @param systemInfo - Optional system master data (from /v1/senec/anlagen)
32
+ * @throws {Error} If apiResponse is null, undefined, or invalid
33
+ */
34
+ parseEnergyData(apiResponse: any, systemInfo?: any): SenecData;
35
+ /**
36
+ * Validate parsed data
37
+ * @param data - Parsed SENEC data to validate
38
+ * @returns Validation result with errors and warnings
39
+ */
40
+ validateData(data: SenecData): ValidationResult;
41
+ /**
42
+ * Extract value from nested object using dot notation path
43
+ * @param obj - Source object
44
+ * @param path - Dot notation path (e.g., 'grid.import.today')
45
+ * @param defaultValue - Default value if path not found
46
+ * @returns Extracted value or default value
47
+ */
48
+ private extractValue;
49
+ /**
50
+ * Log debug message
51
+ * @param method - Method name where log originated
52
+ * @param message - Debug message
53
+ */
54
+ private logDebug;
55
+ /**
56
+ * Log warning message
57
+ * @param method - Method name where warning originated
58
+ * @param message - Warning message
59
+ */
60
+ private logWarning;
61
+ /**
62
+ * Log error message
63
+ * @param method - Method name where error originated
64
+ * @param error - Error object
65
+ */
66
+ private logError;
67
+ }
68
+ //# sourceMappingURL=data-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-parser.d.ts","sourceRoot":"","sources":["../../src/lib/data-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE7D,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAU;gBAEf,SAAS,GAAE,OAAe;IAItC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,eAAe,CAAC,WAAW,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,GAAG,SAAS;IAkD9D;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,gBAAgB;IA2D/C;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAMhB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,QAAQ;CAMjB"}
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ /**
3
+ * Data Parser
4
+ *
5
+ * Parses SENEC Cloud App API dashboard responses (v2 format from
6
+ * app-gateway.prod.senec.dev) and transforms them into the expected
7
+ * payload structure for backward compatibility.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.DataParser = void 0;
11
+ class DataParser {
12
+ constructor(debugMode = false) {
13
+ this.debugMode = debugMode;
14
+ }
15
+ /**
16
+ * Parse SENEC Cloud App API v2 dashboard response into structured data.
17
+ *
18
+ * Expected input structure (v2/senec/systems/{id}/dashboard):
19
+ * {
20
+ * currently: {
21
+ * powerGenerationInW, powerConsumptionInW,
22
+ * gridFeedInInW, gridDrawInW,
23
+ * batteryChargeInW, batteryDischargeInW,
24
+ * batteryLevelInPercent, selfSufficiencyInPercent, wallboxInW
25
+ * },
26
+ * today: {
27
+ * powerGenerationInWh, powerConsumptionInWh,
28
+ * gridFeedInInWh, gridDrawInWh, ...
29
+ * },
30
+ * timestamp: ISO string
31
+ * }
32
+ *
33
+ * @param apiResponse - Raw dashboard API response from SENEC Cloud
34
+ * @param systemInfo - Optional system master data (from /v1/senec/anlagen)
35
+ * @throws {Error} If apiResponse is null, undefined, or invalid
36
+ */
37
+ parseEnergyData(apiResponse, systemInfo) {
38
+ try {
39
+ // Validate input
40
+ if (!apiResponse || typeof apiResponse !== 'object') {
41
+ const error = new Error('Invalid API response: response must be a non-null object');
42
+ this.logError('parseEnergyData', error);
43
+ throw error;
44
+ }
45
+ // Today values come in Wh from the API; old payload used kWh
46
+ const whToKwh = (value) => Math.round((value / 1000) * 1000) / 1000;
47
+ const parsedData = {
48
+ steuereinheitState: this.extractValue(systemInfo, 'systemType', 'OK'),
49
+ gridimport: {
50
+ today: whToKwh(this.extractValue(apiResponse, 'today.gridDrawInWh', 0)),
51
+ now: this.extractValue(apiResponse, 'currently.gridDrawInW', 0),
52
+ },
53
+ powergenerated: {
54
+ today: whToKwh(this.extractValue(apiResponse, 'today.powerGenerationInWh', 0)),
55
+ now: this.extractValue(apiResponse, 'currently.powerGenerationInW', 0),
56
+ },
57
+ consumption: {
58
+ today: whToKwh(this.extractValue(apiResponse, 'today.powerConsumptionInWh', 0)),
59
+ now: this.extractValue(apiResponse, 'currently.powerConsumptionInW', 0),
60
+ },
61
+ gridexport: {
62
+ today: whToKwh(this.extractValue(apiResponse, 'today.gridFeedInInWh', 0)),
63
+ now: this.extractValue(apiResponse, 'currently.gridFeedInInW', 0),
64
+ },
65
+ acculevel: {
66
+ now: this.extractValue(apiResponse, 'currently.batteryLevelInPercent', 0),
67
+ },
68
+ };
69
+ this.logDebug('parseEnergyData', 'Successfully parsed SENEC energy data');
70
+ return parsedData;
71
+ }
72
+ catch (error) {
73
+ const errorMessage = `Failed to parse energy data: ${error instanceof Error ? error.message : 'Unknown error'}`;
74
+ this.logError('parseEnergyData', new Error(errorMessage));
75
+ throw new Error(errorMessage);
76
+ }
77
+ }
78
+ /**
79
+ * Validate parsed data
80
+ * @param data - Parsed SENEC data to validate
81
+ * @returns Validation result with errors and warnings
82
+ */
83
+ validateData(data) {
84
+ try {
85
+ const errors = [];
86
+ const warnings = [];
87
+ // Validate battery level
88
+ if (data.acculevel.now < 0 || data.acculevel.now > 100) {
89
+ const error = `Invalid battery level: ${data.acculevel.now}% (must be 0-100)`;
90
+ errors.push(error);
91
+ this.logError('validateData', new Error(error));
92
+ }
93
+ // Validate power values (should be non-negative)
94
+ if (data.powergenerated.now < 0) {
95
+ const warning = `Negative solar production: ${data.powergenerated.now}W`;
96
+ warnings.push(warning);
97
+ this.logWarning('validateData', warning);
98
+ }
99
+ if (data.consumption.now < 0) {
100
+ const warning = `Negative consumption: ${data.consumption.now}W`;
101
+ warnings.push(warning);
102
+ this.logWarning('validateData', warning);
103
+ }
104
+ // Check for suspiciously high values (>100kW for home system)
105
+ const maxPower = 100000; // 100kW
106
+ if (data.powergenerated.now > maxPower) {
107
+ const warning = `Unusually high solar production: ${data.powergenerated.now}W`;
108
+ warnings.push(warning);
109
+ this.logWarning('validateData', warning);
110
+ }
111
+ if (data.consumption.now > maxPower) {
112
+ const warning = `Unusually high consumption: ${data.consumption.now}W`;
113
+ warnings.push(warning);
114
+ this.logWarning('validateData', warning);
115
+ }
116
+ const result = {
117
+ valid: errors.length === 0,
118
+ errors,
119
+ warnings,
120
+ };
121
+ if (result.valid) {
122
+ this.logDebug('validateData', 'Data validation passed');
123
+ }
124
+ else {
125
+ this.logError('validateData', new Error(`Data validation failed with ${errors.length} error(s)`));
126
+ }
127
+ return result;
128
+ }
129
+ catch (error) {
130
+ const errorMessage = `Validation process failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
131
+ this.logError('validateData', new Error(errorMessage));
132
+ throw new Error(errorMessage);
133
+ }
134
+ }
135
+ /**
136
+ * Extract value from nested object using dot notation path
137
+ * @param obj - Source object
138
+ * @param path - Dot notation path (e.g., 'grid.import.today')
139
+ * @param defaultValue - Default value if path not found
140
+ * @returns Extracted value or default value
141
+ */
142
+ extractValue(obj, path, defaultValue) {
143
+ try {
144
+ const keys = path.split('.');
145
+ let current = obj;
146
+ for (const key of keys) {
147
+ if (current && typeof current === 'object' && key in current) {
148
+ current = current[key];
149
+ }
150
+ else {
151
+ this.logDebug('extractValue', `Path '${path}' not found, using default value`);
152
+ return defaultValue;
153
+ }
154
+ }
155
+ const result = current !== undefined && current !== null ? current : defaultValue;
156
+ if (result === defaultValue) {
157
+ this.logDebug('extractValue', `Path '${path}' returned null/undefined, using default value`);
158
+ }
159
+ return result;
160
+ }
161
+ catch (error) {
162
+ this.logError('extractValue', new Error(`Failed to extract value from path '${path}': ${error instanceof Error ? error.message : 'Unknown error'}`));
163
+ return defaultValue;
164
+ }
165
+ }
166
+ /**
167
+ * Log debug message
168
+ * @param method - Method name where log originated
169
+ * @param message - Debug message
170
+ */
171
+ logDebug(method, message) {
172
+ if (this.debugMode) {
173
+ process.stderr.write(`[DataParser.${method}] DEBUG: ${message}\n`);
174
+ }
175
+ }
176
+ /**
177
+ * Log warning message
178
+ * @param method - Method name where warning originated
179
+ * @param message - Warning message
180
+ */
181
+ logWarning(method, message) {
182
+ process.stderr.write(`[DataParser.${method}] WARNING: ${message}\n`);
183
+ }
184
+ /**
185
+ * Log error message
186
+ * @param method - Method name where error originated
187
+ * @param error - Error object
188
+ */
189
+ logError(method, error) {
190
+ process.stderr.write(`[DataParser.${method}] ERROR: ${error.message}\n`);
191
+ if (this.debugMode && error.stack) {
192
+ process.stderr.write(`${error.stack}\n`);
193
+ }
194
+ }
195
+ }
196
+ exports.DataParser = DataParser;
197
+ //# sourceMappingURL=data-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-parser.js","sourceRoot":"","sources":["../../src/lib/data-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Error Handler
3
+ *
4
+ * Centralized error handling with classification and retry logic.
5
+ */
6
+ import { AxiosError } from 'axios';
7
+ import { ErrorType, HandledError } from '../types/senec';
8
+ export declare class ErrorHandler {
9
+ /**
10
+ * Handle and classify an error
11
+ */
12
+ handleError(error: Error | AxiosError): HandledError;
13
+ /**
14
+ * Classify error by type
15
+ */
16
+ classifyError(error: Error | AxiosError): ErrorType;
17
+ /**
18
+ * Determine if error should be retried
19
+ */
20
+ shouldRetry(error: Error | AxiosError): boolean;
21
+ /**
22
+ * Calculate retry delay with exponential backoff
23
+ * @param attempt - Retry attempt number (1-based)
24
+ */
25
+ getRetryDelay(attempt: number): number;
26
+ /**
27
+ * Get Retry-After header value if present (for rate limiting)
28
+ */
29
+ getRetryAfter(error: AxiosError): number | null;
30
+ /**
31
+ * Get user-friendly error message
32
+ */
33
+ private getErrorMessage;
34
+ /**
35
+ * Type guard for AxiosError
36
+ */
37
+ private isAxiosError;
38
+ }
39
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/lib/error-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEzD,qBAAa,YAAY;IACvB;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,YAAY;IAcpD;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS;IA8BnD;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,GAAG,OAAO;IAsB/C;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAgBtC;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;IAsB/C;;OAEG;IACH,OAAO,CAAC,eAAe;IA4BvB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}