@aifabrix/builder 2.10.0 → 2.11.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.
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy.json +12 -4
- package/lib/app-config.js +39 -8
- package/lib/app-register.js +70 -388
- package/lib/templates.js +8 -3
- package/lib/utils/api-error-handler.js +11 -453
- package/lib/utils/app-register-api.js +71 -0
- package/lib/utils/app-register-auth.js +72 -0
- package/lib/utils/app-register-config.js +205 -0
- package/lib/utils/app-register-display.js +69 -0
- package/lib/utils/app-register-validator.js +143 -0
- package/lib/utils/device-code.js +1 -1
- package/lib/utils/error-formatters/error-parser.js +150 -0
- package/lib/utils/error-formatters/http-status-errors.js +189 -0
- package/lib/utils/error-formatters/network-errors.js +46 -0
- package/lib/utils/error-formatters/permission-errors.js +94 -0
- package/lib/utils/error-formatters/validation-errors.js +133 -0
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Register Configuration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Configuration extraction and loading for application registration
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Configuration utilities for app registration
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs').promises;
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const logger = require('./logger');
|
|
16
|
+
const { detectAppType } = require('./paths');
|
|
17
|
+
|
|
18
|
+
// Import createApp to auto-generate config if missing
|
|
19
|
+
let createApp;
|
|
20
|
+
try {
|
|
21
|
+
createApp = require('../app').createApp;
|
|
22
|
+
} catch {
|
|
23
|
+
createApp = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Load variables.yaml file for an application
|
|
28
|
+
* @async
|
|
29
|
+
* @param {string} appKey - Application key
|
|
30
|
+
* @returns {Promise<{variables: Object, created: boolean}>} Variables and creation flag
|
|
31
|
+
*/
|
|
32
|
+
async function loadVariablesYaml(appKey) {
|
|
33
|
+
// Detect app type and get correct path (integration or builder)
|
|
34
|
+
const { appPath } = await detectAppType(appKey);
|
|
35
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
39
|
+
return { variables: yaml.load(variablesContent), created: false };
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT') {
|
|
42
|
+
logger.log(chalk.yellow(`⚠️ variables.yaml not found for ${appKey}`));
|
|
43
|
+
logger.log(chalk.yellow('📝 Creating minimal configuration...\n'));
|
|
44
|
+
return { variables: null, created: true };
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Failed to read variables.yaml: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create minimal application configuration if needed
|
|
52
|
+
* @async
|
|
53
|
+
* @param {string} appKey - Application key
|
|
54
|
+
* @param {Object} options - Registration options
|
|
55
|
+
* @returns {Promise<Object>} Variables after creation
|
|
56
|
+
*/
|
|
57
|
+
async function createMinimalAppIfNeeded(appKey, options) {
|
|
58
|
+
if (!createApp) {
|
|
59
|
+
throw new Error('Cannot auto-create application: createApp function not available');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await createApp(appKey, {
|
|
63
|
+
port: options.port,
|
|
64
|
+
language: 'typescript',
|
|
65
|
+
database: false,
|
|
66
|
+
redis: false,
|
|
67
|
+
storage: false,
|
|
68
|
+
authentication: false
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Detect app type and get correct path (integration or builder)
|
|
72
|
+
const { appPath } = await detectAppType(appKey);
|
|
73
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
74
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
75
|
+
return yaml.load(variablesContent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Builds image reference string from variables
|
|
80
|
+
* Format: repository:tag (e.g., aifabrix/miso-controller:latest or myregistry.azurecr.io/miso-controller:v1.0.0)
|
|
81
|
+
* @param {Object} variables - Variables from YAML file
|
|
82
|
+
* @param {string} appKey - Application key (fallback)
|
|
83
|
+
* @returns {string} Image reference string
|
|
84
|
+
*/
|
|
85
|
+
function buildImageReference(variables, appKey) {
|
|
86
|
+
const imageName = variables.image?.name || variables.app?.key || appKey;
|
|
87
|
+
const registry = variables.image?.registry;
|
|
88
|
+
const tag = variables.image?.tag || 'latest';
|
|
89
|
+
return registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract URL from external system JSON file for registration
|
|
94
|
+
* @async
|
|
95
|
+
* @param {string} appKey - Application key
|
|
96
|
+
* @param {Object} externalIntegration - External integration config from variables.yaml
|
|
97
|
+
* @returns {Promise<{url: string, apiKey?: string}>} URL and optional API key
|
|
98
|
+
*/
|
|
99
|
+
async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
100
|
+
if (!externalIntegration || !externalIntegration.systems || externalIntegration.systems.length === 0) {
|
|
101
|
+
throw new Error('externalIntegration.systems is required for external type applications');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Detect app type and get correct path (integration or builder)
|
|
105
|
+
const { appPath } = await detectAppType(appKey);
|
|
106
|
+
const schemaBasePath = externalIntegration.schemaBasePath || './';
|
|
107
|
+
const systemFileName = externalIntegration.systems[0];
|
|
108
|
+
|
|
109
|
+
// Resolve system file path (handle both relative and absolute paths)
|
|
110
|
+
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
111
|
+
? path.join(schemaBasePath, systemFileName)
|
|
112
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const systemContent = await fs.readFile(systemFilePath, 'utf-8');
|
|
116
|
+
const systemJson = JSON.parse(systemContent);
|
|
117
|
+
|
|
118
|
+
// Extract URL from environment.baseUrl
|
|
119
|
+
const url = systemJson.environment?.baseUrl;
|
|
120
|
+
if (!url) {
|
|
121
|
+
throw new Error(`Missing environment.baseUrl in ${systemFileName}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Extract optional API key from authentication if present
|
|
125
|
+
let apiKey;
|
|
126
|
+
if (systemJson.authentication?.apikey?.key) {
|
|
127
|
+
// If it's a kv:// reference, we can't resolve it here, so leave it undefined
|
|
128
|
+
// The API will handle kv:// references
|
|
129
|
+
const keyValue = systemJson.authentication.apikey.key;
|
|
130
|
+
if (!keyValue.startsWith('kv://') && !keyValue.startsWith('{{')) {
|
|
131
|
+
apiKey = keyValue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { url, apiKey };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error.code === 'ENOENT') {
|
|
138
|
+
throw new Error(`External system file not found: ${systemFilePath}`);
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Failed to read external system file: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extract application configuration from variables.yaml
|
|
146
|
+
* @async
|
|
147
|
+
* @param {Object} variables - Variables from YAML file
|
|
148
|
+
* @param {string} appKey - Application key
|
|
149
|
+
* @param {Object} options - Registration options
|
|
150
|
+
* @returns {Promise<Object>} Extracted configuration
|
|
151
|
+
*/
|
|
152
|
+
async function extractAppConfiguration(variables, appKey, options) {
|
|
153
|
+
const appKeyFromFile = variables.app?.key || appKey;
|
|
154
|
+
const displayName = variables.app?.name || options.name || appKey;
|
|
155
|
+
const description = variables.app?.description || '';
|
|
156
|
+
|
|
157
|
+
// Handle external type
|
|
158
|
+
if (variables.app?.type === 'external') {
|
|
159
|
+
// Extract URL from external system JSON file
|
|
160
|
+
const { url, apiKey } = await extractExternalIntegrationUrl(appKey, variables.externalIntegration);
|
|
161
|
+
|
|
162
|
+
// Build simplified externalIntegration object for registration API
|
|
163
|
+
const externalIntegration = { url };
|
|
164
|
+
if (apiKey) {
|
|
165
|
+
externalIntegration.apiKey = apiKey;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
appKey: appKeyFromFile,
|
|
170
|
+
displayName,
|
|
171
|
+
description,
|
|
172
|
+
appType: 'external',
|
|
173
|
+
externalIntegration,
|
|
174
|
+
port: null, // External systems don't need ports
|
|
175
|
+
image: null, // External systems don't need images
|
|
176
|
+
language: null // External systems don't need language
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const appType = variables.build?.language === 'typescript' ? 'webapp' : 'service';
|
|
181
|
+
const registryMode = variables.image?.registryMode || 'external';
|
|
182
|
+
const port = variables.build?.port || options.port || 3000;
|
|
183
|
+
const language = variables.build?.language || 'typescript';
|
|
184
|
+
const image = buildImageReference(variables, appKeyFromFile);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
appKey: appKeyFromFile,
|
|
188
|
+
displayName,
|
|
189
|
+
description,
|
|
190
|
+
appType,
|
|
191
|
+
registryMode,
|
|
192
|
+
port,
|
|
193
|
+
image,
|
|
194
|
+
language
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
loadVariablesYaml,
|
|
200
|
+
createMinimalAppIfNeeded,
|
|
201
|
+
buildImageReference,
|
|
202
|
+
extractAppConfiguration,
|
|
203
|
+
extractExternalIntegrationUrl
|
|
204
|
+
};
|
|
205
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Register Display Utilities
|
|
3
|
+
*
|
|
4
|
+
* Display and formatting utilities for application registration results
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Display utilities for app registration
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get environment prefix for GitHub Secrets
|
|
16
|
+
* @param {string} environment - Environment key (e.g., 'dev', 'tst', 'pro', 'miso')
|
|
17
|
+
* @returns {string} Uppercase prefix (e.g., 'DEV', 'TST', 'PRO', 'MISO')
|
|
18
|
+
*/
|
|
19
|
+
function getEnvironmentPrefix(environment) {
|
|
20
|
+
if (!environment) {
|
|
21
|
+
return 'DEV';
|
|
22
|
+
}
|
|
23
|
+
// Convert to uppercase and handle common variations
|
|
24
|
+
const env = environment.toLowerCase();
|
|
25
|
+
if (env === 'dev' || env === 'development') {
|
|
26
|
+
return 'DEV';
|
|
27
|
+
}
|
|
28
|
+
if (env === 'tst' || env === 'test' || env === 'staging') {
|
|
29
|
+
return 'TST';
|
|
30
|
+
}
|
|
31
|
+
if (env === 'pro' || env === 'prod' || env === 'production') {
|
|
32
|
+
return 'PRO';
|
|
33
|
+
}
|
|
34
|
+
// For other environments (e.g., 'miso'), uppercase the entire string
|
|
35
|
+
// Use full string if 4 characters or less, otherwise use first 4 characters
|
|
36
|
+
const upper = environment.toUpperCase();
|
|
37
|
+
return upper.length <= 4 ? upper : upper.substring(0, 4);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Display registration success and credentials
|
|
42
|
+
* @param {Object} data - Registration response data
|
|
43
|
+
* @param {string} apiUrl - API URL
|
|
44
|
+
* @param {string} environment - Environment key
|
|
45
|
+
*/
|
|
46
|
+
function displayRegistrationResults(data, apiUrl, environment) {
|
|
47
|
+
logger.log(chalk.green('✅ Application registered successfully!\n'));
|
|
48
|
+
logger.log(chalk.bold('📋 Application Details:'));
|
|
49
|
+
logger.log(` ID: ${data.application.id}`);
|
|
50
|
+
logger.log(` Key: ${data.application.key}`);
|
|
51
|
+
logger.log(` Display Name: ${data.application.displayName}\n`);
|
|
52
|
+
|
|
53
|
+
logger.log(chalk.bold.yellow('🔑 CREDENTIALS (save these immediately):'));
|
|
54
|
+
logger.log(chalk.yellow(` Client ID: ${data.credentials.clientId}`));
|
|
55
|
+
logger.log(chalk.yellow(` Client Secret: ${data.credentials.clientSecret}\n`));
|
|
56
|
+
|
|
57
|
+
logger.log(chalk.red('⚠️ IMPORTANT: Client Secret will not be shown again!\n'));
|
|
58
|
+
|
|
59
|
+
const envPrefix = getEnvironmentPrefix(environment);
|
|
60
|
+
logger.log(chalk.bold('📝 Add to GitHub Secrets:'));
|
|
61
|
+
logger.log(chalk.cyan(' Repository level:'));
|
|
62
|
+
logger.log(chalk.cyan(` MISO_CONTROLLER_URL = ${apiUrl}`));
|
|
63
|
+
logger.log(chalk.cyan(`\n Environment level (${environment}):`));
|
|
64
|
+
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTID = ${data.credentials.clientId}`));
|
|
65
|
+
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTSECRET = ${data.credentials.clientSecret}\n`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { displayRegistrationResults, getEnvironmentPrefix };
|
|
69
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Register Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Validation logic for application registration
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Validation utilities for app registration
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const logger = require('./logger');
|
|
14
|
+
const { detectAppType } = require('./paths');
|
|
15
|
+
const applicationSchema = require('../schema/application-schema.json');
|
|
16
|
+
|
|
17
|
+
// Extract valid enum values from application schema
|
|
18
|
+
const validTypes = applicationSchema.properties.type.enum || [];
|
|
19
|
+
const validRegistryModes = applicationSchema.properties.registryMode.enum || [];
|
|
20
|
+
const portConstraints = {
|
|
21
|
+
minimum: applicationSchema.properties.port?.minimum || 1,
|
|
22
|
+
maximum: applicationSchema.properties.port?.maximum || 65535
|
|
23
|
+
};
|
|
24
|
+
const imagePattern = applicationSchema.properties.image?.pattern || /^[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validation schema for application registration
|
|
28
|
+
* Validates according to application-schema.json
|
|
29
|
+
*/
|
|
30
|
+
const registerApplicationSchema = {
|
|
31
|
+
environmentId: (val) => {
|
|
32
|
+
if (!val || val.length < 1) {
|
|
33
|
+
throw new Error('Invalid environment ID format');
|
|
34
|
+
}
|
|
35
|
+
return val;
|
|
36
|
+
},
|
|
37
|
+
key: (val) => {
|
|
38
|
+
if (!val || val.length < 1) {
|
|
39
|
+
throw new Error('Application key is required');
|
|
40
|
+
}
|
|
41
|
+
const keyPattern = applicationSchema.properties.key.pattern;
|
|
42
|
+
const keyMaxLength = applicationSchema.properties.key.maxLength || 50;
|
|
43
|
+
if (val.length > keyMaxLength) {
|
|
44
|
+
throw new Error(`Application key must be at most ${keyMaxLength} characters`);
|
|
45
|
+
}
|
|
46
|
+
if (keyPattern && !new RegExp(keyPattern).test(val)) {
|
|
47
|
+
throw new Error('Application key must contain only lowercase letters, numbers, and hyphens');
|
|
48
|
+
}
|
|
49
|
+
return val;
|
|
50
|
+
},
|
|
51
|
+
displayName: (val) => {
|
|
52
|
+
if (!val || val.length < 1) {
|
|
53
|
+
throw new Error('Display name is required');
|
|
54
|
+
}
|
|
55
|
+
const displayNameMaxLength = applicationSchema.properties.displayName.maxLength || 100;
|
|
56
|
+
if (val.length > displayNameMaxLength) {
|
|
57
|
+
throw new Error(`Display name must be at most ${displayNameMaxLength} characters`);
|
|
58
|
+
}
|
|
59
|
+
return val;
|
|
60
|
+
},
|
|
61
|
+
description: (val) => val || undefined,
|
|
62
|
+
image: (val, appType) => {
|
|
63
|
+
// Image is required for non-external types
|
|
64
|
+
if (appType !== 'external') {
|
|
65
|
+
if (!val || typeof val !== 'string') {
|
|
66
|
+
throw new Error('Image is required for non-external application types');
|
|
67
|
+
}
|
|
68
|
+
// Validate image format: repository:tag
|
|
69
|
+
const pattern = typeof imagePattern === 'string' ? new RegExp(imagePattern) : imagePattern;
|
|
70
|
+
if (!pattern.test(val)) {
|
|
71
|
+
throw new Error('Image must be in format repository:tag (e.g., aifabrix/miso-controller:latest or myregistry.azurecr.io/miso-controller:v1.0.0)');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return val || undefined;
|
|
75
|
+
},
|
|
76
|
+
configuration: (val) => {
|
|
77
|
+
if (!val || !val.type || !validTypes.includes(val.type)) {
|
|
78
|
+
throw new Error(`Configuration type must be one of: ${validTypes.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// For external type: require externalIntegration, skip registryMode/port validation
|
|
82
|
+
if (val.type === 'external') {
|
|
83
|
+
if (!val.externalIntegration) {
|
|
84
|
+
throw new Error('externalIntegration is required for external application type');
|
|
85
|
+
}
|
|
86
|
+
// External type should not have registryMode, port, or image
|
|
87
|
+
return val;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// For non-external types: require registryMode, port, image
|
|
91
|
+
if (!val.registryMode || !validRegistryModes.includes(val.registryMode)) {
|
|
92
|
+
throw new Error(`Registry mode must be one of: ${validRegistryModes.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
if (val.port === undefined || val.port === null) {
|
|
95
|
+
throw new Error('Port is required for non-external application types');
|
|
96
|
+
}
|
|
97
|
+
if (!Number.isInteger(val.port) || val.port < portConstraints.minimum || val.port > portConstraints.maximum) {
|
|
98
|
+
throw new Error(`Port must be an integer between ${portConstraints.minimum} and ${portConstraints.maximum}`);
|
|
99
|
+
}
|
|
100
|
+
return val;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate application registration data
|
|
106
|
+
* @async
|
|
107
|
+
* @param {Object} config - Application configuration
|
|
108
|
+
* @param {string} originalAppKey - Original app key for error messages
|
|
109
|
+
* @throws {Error} If validation fails
|
|
110
|
+
*/
|
|
111
|
+
async function validateAppRegistrationData(config, originalAppKey) {
|
|
112
|
+
const missingFields = [];
|
|
113
|
+
if (!config.appKey) missingFields.push('app.key');
|
|
114
|
+
if (!config.displayName) missingFields.push('app.name');
|
|
115
|
+
|
|
116
|
+
if (missingFields.length > 0) {
|
|
117
|
+
logger.error(chalk.red('❌ Missing required fields in variables.yaml:'));
|
|
118
|
+
missingFields.forEach(field => logger.error(chalk.red(` - ${field}`)));
|
|
119
|
+
// Detect app type to show correct path
|
|
120
|
+
const { appPath } = await detectAppType(originalAppKey);
|
|
121
|
+
const relativePath = path.relative(process.cwd(), appPath);
|
|
122
|
+
logger.error(chalk.red(`\n Please update ${relativePath}/variables.yaml and try again.`));
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
registerApplicationSchema.key(config.appKey);
|
|
128
|
+
registerApplicationSchema.displayName(config.displayName);
|
|
129
|
+
registerApplicationSchema.image(config.image, config.appType);
|
|
130
|
+
registerApplicationSchema.configuration({
|
|
131
|
+
type: config.appType,
|
|
132
|
+
registryMode: config.registryMode,
|
|
133
|
+
port: config.port,
|
|
134
|
+
externalIntegration: config.externalIntegration
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error(chalk.red(`❌ Invalid configuration: ${error.message}`));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { registerApplicationSchema, validateAppRegistrationData };
|
|
143
|
+
|
package/lib/utils/device-code.js
CHANGED
|
@@ -388,7 +388,7 @@ async function refreshDeviceToken(controllerUrl, refreshToken) {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const url = `${controllerUrl}/api/v1/auth/login/device/refresh`;
|
|
391
|
-
const response = await
|
|
391
|
+
const response = await getMakeApiCall()(url, {
|
|
392
392
|
method: 'POST',
|
|
393
393
|
headers: {
|
|
394
394
|
'Content-Type': 'application/json'
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Parser Utilities
|
|
3
|
+
*
|
|
4
|
+
* Parses error responses and determines error types
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Error parsing utilities
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { formatPermissionError } = require('./permission-errors');
|
|
12
|
+
const { formatValidationError } = require('./validation-errors');
|
|
13
|
+
const {
|
|
14
|
+
formatAuthenticationError,
|
|
15
|
+
formatServerError,
|
|
16
|
+
formatConflictError,
|
|
17
|
+
formatNotFoundError,
|
|
18
|
+
formatGenericError
|
|
19
|
+
} = require('./http-status-errors');
|
|
20
|
+
const { formatNetworkError } = require('./network-errors');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parses error response into error data object
|
|
24
|
+
* @param {string|Object} errorResponse - Error response (string or parsed JSON)
|
|
25
|
+
* @returns {Object} Parsed error data object
|
|
26
|
+
*/
|
|
27
|
+
function parseErrorData(errorResponse) {
|
|
28
|
+
if (errorResponse === undefined || errorResponse === null) {
|
|
29
|
+
return { message: 'Unknown error occurred' };
|
|
30
|
+
}
|
|
31
|
+
if (typeof errorResponse === 'string') {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(errorResponse);
|
|
34
|
+
} catch {
|
|
35
|
+
return { message: errorResponse };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return typeof errorResponse === 'object' ? errorResponse : { message: String(errorResponse) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates error result object
|
|
43
|
+
* @param {string} type - Error type
|
|
44
|
+
* @param {string} message - Error message
|
|
45
|
+
* @param {string} formatted - Formatted error message
|
|
46
|
+
* @param {Object} data - Error data
|
|
47
|
+
* @returns {Object} Error result object
|
|
48
|
+
*/
|
|
49
|
+
function createErrorResult(type, message, formatted, data) {
|
|
50
|
+
return { type, message, formatted, data };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Gets error message from error data
|
|
55
|
+
* @param {Object} errorData - Error data object
|
|
56
|
+
* @param {string} defaultMessage - Default message if not found
|
|
57
|
+
* @returns {string} Error message
|
|
58
|
+
*/
|
|
59
|
+
function getErrorMessage(errorData, defaultMessage) {
|
|
60
|
+
return errorData.detail || errorData.title || errorData.errorDescription ||
|
|
61
|
+
errorData.message || errorData.error || defaultMessage;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Handles 400 validation error
|
|
66
|
+
* @param {Object} errorData - Error data object
|
|
67
|
+
* @returns {Object} Error result object
|
|
68
|
+
*/
|
|
69
|
+
function handleValidationError(errorData) {
|
|
70
|
+
const errorMessage = getErrorMessage(errorData, 'Validation error');
|
|
71
|
+
return createErrorResult('validation', errorMessage, formatValidationError(errorData), errorData);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Handles specific 4xx client error codes
|
|
76
|
+
* @param {number} statusCode - HTTP status code
|
|
77
|
+
* @param {Object} errorData - Error data object
|
|
78
|
+
* @returns {Object|null} Error result object or null if not handled
|
|
79
|
+
*/
|
|
80
|
+
function handleSpecificClientErrors(statusCode, errorData) {
|
|
81
|
+
switch (statusCode) {
|
|
82
|
+
case 403:
|
|
83
|
+
return createErrorResult('permission', 'Permission denied', formatPermissionError(errorData), errorData);
|
|
84
|
+
case 401:
|
|
85
|
+
return createErrorResult('authentication', 'Authentication failed', formatAuthenticationError(errorData), errorData);
|
|
86
|
+
case 400:
|
|
87
|
+
case 422:
|
|
88
|
+
return handleValidationError(errorData);
|
|
89
|
+
case 404:
|
|
90
|
+
return createErrorResult('notfound', getErrorMessage(errorData, 'Not found'), formatNotFoundError(errorData), errorData);
|
|
91
|
+
case 409:
|
|
92
|
+
return createErrorResult('conflict', getErrorMessage(errorData, 'Conflict'), formatConflictError(errorData), errorData);
|
|
93
|
+
default:
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handles HTTP status code errors
|
|
100
|
+
* @param {number} statusCode - HTTP status code
|
|
101
|
+
* @param {Object} errorData - Error data object
|
|
102
|
+
* @returns {Object|null} Error result object or null if not handled
|
|
103
|
+
*/
|
|
104
|
+
function handleStatusCodeError(statusCode, errorData) {
|
|
105
|
+
// Handle 4xx client errors
|
|
106
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
107
|
+
return handleSpecificClientErrors(statusCode, errorData);
|
|
108
|
+
}
|
|
109
|
+
// Handle 5xx server errors
|
|
110
|
+
if (statusCode >= 500) {
|
|
111
|
+
return createErrorResult('server', 'Server error', formatServerError(errorData), errorData);
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Parses error response and determines error type
|
|
118
|
+
* @param {string|Object} errorResponse - Error response (string or parsed JSON)
|
|
119
|
+
* @param {number} statusCode - HTTP status code
|
|
120
|
+
* @param {boolean} isNetworkError - Whether this is a network error
|
|
121
|
+
* @returns {Object} Parsed error object with type, message, and formatted output
|
|
122
|
+
*/
|
|
123
|
+
function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
|
|
124
|
+
let errorData = parseErrorData(errorResponse);
|
|
125
|
+
|
|
126
|
+
// Handle nested response structure (some APIs wrap errors in data field)
|
|
127
|
+
if (errorData.data && typeof errorData.data === 'object') {
|
|
128
|
+
errorData = errorData.data;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle network errors
|
|
132
|
+
if (isNetworkError) {
|
|
133
|
+
const errorMessage = errorData.message || errorResponse || 'Network error';
|
|
134
|
+
return createErrorResult('network', errorMessage, formatNetworkError(errorMessage, errorData), errorData);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle HTTP status codes
|
|
138
|
+
const statusError = handleStatusCodeError(statusCode, errorData);
|
|
139
|
+
if (statusError) {
|
|
140
|
+
return statusError;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generic error
|
|
144
|
+
return createErrorResult('generic', errorData.message || errorData.error || 'Unknown error', formatGenericError(errorData, statusCode), errorData);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
parseErrorResponse
|
|
149
|
+
};
|
|
150
|
+
|