@edgible-team/cli 1.0.1

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 (102) hide show
  1. package/LICENSE +136 -0
  2. package/README.md +450 -0
  3. package/dist/client/api-client.js +1057 -0
  4. package/dist/client/index.js +21 -0
  5. package/dist/commands/agent.js +1280 -0
  6. package/dist/commands/ai.js +608 -0
  7. package/dist/commands/application.js +885 -0
  8. package/dist/commands/auth.js +570 -0
  9. package/dist/commands/base/BaseCommand.js +93 -0
  10. package/dist/commands/base/CommandHandler.js +7 -0
  11. package/dist/commands/base/command-wrapper.js +58 -0
  12. package/dist/commands/base/middleware.js +77 -0
  13. package/dist/commands/config.js +116 -0
  14. package/dist/commands/connectivity.js +59 -0
  15. package/dist/commands/debug.js +98 -0
  16. package/dist/commands/discover.js +144 -0
  17. package/dist/commands/examples/migrated-command-example.js +180 -0
  18. package/dist/commands/gateway.js +494 -0
  19. package/dist/commands/managedGateway.js +787 -0
  20. package/dist/commands/utils/config-validator.js +76 -0
  21. package/dist/commands/utils/gateway-prompt.js +79 -0
  22. package/dist/commands/utils/input-parser.js +120 -0
  23. package/dist/commands/utils/output-formatter.js +109 -0
  24. package/dist/config/app-config.js +99 -0
  25. package/dist/detection/SystemCapabilityDetector.js +1244 -0
  26. package/dist/detection/ToolDetector.js +305 -0
  27. package/dist/detection/WorkloadDetector.js +314 -0
  28. package/dist/di/bindings.js +99 -0
  29. package/dist/di/container.js +88 -0
  30. package/dist/di/types.js +32 -0
  31. package/dist/index.js +52 -0
  32. package/dist/interfaces/IDaemonManager.js +3 -0
  33. package/dist/repositories/config-repository.js +62 -0
  34. package/dist/repositories/gateway-repository.js +35 -0
  35. package/dist/scripts/postinstall.js +101 -0
  36. package/dist/services/AgentStatusManager.js +299 -0
  37. package/dist/services/ConnectivityTester.js +271 -0
  38. package/dist/services/DependencyInstaller.js +475 -0
  39. package/dist/services/LocalAgentManager.js +2216 -0
  40. package/dist/services/application/ApplicationService.js +299 -0
  41. package/dist/services/auth/AuthService.js +214 -0
  42. package/dist/services/aws.js +644 -0
  43. package/dist/services/daemon/DaemonManagerFactory.js +65 -0
  44. package/dist/services/daemon/DockerDaemonManager.js +395 -0
  45. package/dist/services/daemon/LaunchdDaemonManager.js +257 -0
  46. package/dist/services/daemon/PodmanDaemonManager.js +369 -0
  47. package/dist/services/daemon/SystemdDaemonManager.js +221 -0
  48. package/dist/services/daemon/WindowsServiceDaemonManager.js +210 -0
  49. package/dist/services/daemon/index.js +16 -0
  50. package/dist/services/edgible.js +3060 -0
  51. package/dist/services/gateway/GatewayService.js +334 -0
  52. package/dist/state/config.js +146 -0
  53. package/dist/types/AgentConfig.js +5 -0
  54. package/dist/types/AgentStatus.js +5 -0
  55. package/dist/types/ApiClient.js +5 -0
  56. package/dist/types/ApiRequests.js +5 -0
  57. package/dist/types/ApiResponses.js +5 -0
  58. package/dist/types/Application.js +5 -0
  59. package/dist/types/CaddyJson.js +5 -0
  60. package/dist/types/UnifiedAgentStatus.js +56 -0
  61. package/dist/types/WireGuard.js +5 -0
  62. package/dist/types/Workload.js +5 -0
  63. package/dist/types/agent.js +5 -0
  64. package/dist/types/command-options.js +5 -0
  65. package/dist/types/connectivity.js +5 -0
  66. package/dist/types/errors.js +250 -0
  67. package/dist/types/gateway-types.js +5 -0
  68. package/dist/types/index.js +48 -0
  69. package/dist/types/models/ApplicationData.js +5 -0
  70. package/dist/types/models/CertificateData.js +5 -0
  71. package/dist/types/models/DeviceData.js +5 -0
  72. package/dist/types/models/DevicePoolData.js +5 -0
  73. package/dist/types/models/OrganizationData.js +5 -0
  74. package/dist/types/models/OrganizationInviteData.js +5 -0
  75. package/dist/types/models/ProviderConfiguration.js +5 -0
  76. package/dist/types/models/ResourceData.js +5 -0
  77. package/dist/types/models/ServiceResourceData.js +5 -0
  78. package/dist/types/models/UserData.js +5 -0
  79. package/dist/types/route.js +5 -0
  80. package/dist/types/validation/schemas.js +218 -0
  81. package/dist/types/validation.js +5 -0
  82. package/dist/utils/FileIntegrityManager.js +256 -0
  83. package/dist/utils/PathMigration.js +219 -0
  84. package/dist/utils/PathResolver.js +235 -0
  85. package/dist/utils/PlatformDetector.js +277 -0
  86. package/dist/utils/console-logger.js +130 -0
  87. package/dist/utils/docker-compose-parser.js +179 -0
  88. package/dist/utils/errors.js +130 -0
  89. package/dist/utils/health-checker.js +155 -0
  90. package/dist/utils/json-logger.js +72 -0
  91. package/dist/utils/log-formatter.js +293 -0
  92. package/dist/utils/logger.js +59 -0
  93. package/dist/utils/network-utils.js +217 -0
  94. package/dist/utils/output.js +182 -0
  95. package/dist/utils/passwordValidation.js +91 -0
  96. package/dist/utils/progress.js +167 -0
  97. package/dist/utils/sudo-checker.js +22 -0
  98. package/dist/utils/urls.js +32 -0
  99. package/dist/utils/validation.js +31 -0
  100. package/dist/validation/schemas.js +175 -0
  101. package/dist/validation/validator.js +67 -0
  102. package/package.json +83 -0
@@ -0,0 +1,1057 @@
1
+ "use strict";
2
+ // AUTO-GENERATED FILE - DO NOT EDIT
3
+ // This file is copied from backend during build. Changes will be overwritten.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.ApiClient = exports.ApiClientError = void 0;
6
+ exports.createApiClient = createApiClient;
7
+ // ============================================================================
8
+ // DEBUG UTILITY
9
+ // ============================================================================
10
+ // Note: debugLog function removed - now using structured logging through log() method
11
+ // ============================================================================
12
+ // BASE API CLIENT
13
+ // ============================================================================
14
+ class BaseApiClient {
15
+ constructor(config) {
16
+ this.refreshPromise = null;
17
+ this.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash
18
+ this.timeout = config.timeout || 30000;
19
+ this.disableCaching = false;
20
+ this.logFunction = config.logFunction;
21
+ this.apiClientLogger = config.apiClientLogger;
22
+ }
23
+ setAccessToken(token) {
24
+ this.accessToken = token;
25
+ }
26
+ setIdToken(token) {
27
+ this.idToken = token;
28
+ }
29
+ setRefreshToken(token) {
30
+ this.refreshTokenValue = token;
31
+ }
32
+ clearAccessToken() {
33
+ delete this.accessToken;
34
+ }
35
+ clearIdToken() {
36
+ delete this.idToken;
37
+ }
38
+ clearRefreshToken() {
39
+ delete this.refreshTokenValue;
40
+ }
41
+ clearTokens() {
42
+ delete this.accessToken;
43
+ delete this.idToken;
44
+ delete this.refreshTokenValue;
45
+ }
46
+ /**
47
+ * Check if tokens are expired or about to expire
48
+ */
49
+ isTokenExpired() {
50
+ if (!this.idToken)
51
+ return true;
52
+ try {
53
+ // Basic JWT parsing to check expiration
54
+ const parts = this.idToken.split('.');
55
+ if (parts.length !== 3)
56
+ return true;
57
+ const payload = JSON.parse(atob(parts[1] || ''));
58
+ const now = Math.floor(Date.now() / 1000);
59
+ const buffer = 300; // 5 minute buffer
60
+ return payload.exp && (payload.exp - buffer) <= now;
61
+ }
62
+ catch (error) {
63
+ // If we can't parse the token, consider it expired
64
+ return true;
65
+ }
66
+ }
67
+ /**
68
+ * Ensure tokens are valid before making a request
69
+ * Override in subclasses to implement token refresh logic
70
+ * @returns Promise<boolean> - true if tokens are valid, false otherwise
71
+ */
72
+ async ensureValidTokens() {
73
+ // Default implementation: just check if token exists and is not expired
74
+ return !this.isTokenExpired() && !!this.idToken;
75
+ }
76
+ /**
77
+ * Log message (uses passed logger, log function, or falls back to console)
78
+ */
79
+ log(level, message, data) {
80
+ if (this.apiClientLogger) {
81
+ // Use the passed logger instance (preferred for module-based logging)
82
+ this.apiClientLogger.log('api-client', level, message, data);
83
+ }
84
+ else if (this.logFunction) {
85
+ // Use the passed logging function (e.g., from agent)
86
+ this.logFunction(level, message, data);
87
+ }
88
+ else {
89
+ return; // don't log anything
90
+ }
91
+ }
92
+ async makeRequest(method, path, data, requiresAuth = true) {
93
+ const url = `${this.baseUrl}${path}`;
94
+ const requestId = Math.random().toString(36).substring(2, 15);
95
+ // Log request initiation
96
+ this.log('debug', `[${requestId}] Starting API request`, {
97
+ method,
98
+ path,
99
+ url,
100
+ requiresAuth,
101
+ hasData: !!data,
102
+ dataSize: data ? JSON.stringify(data).length : 0,
103
+ timestamp: new Date().toISOString()
104
+ });
105
+ const headers = {
106
+ 'Content-Type': 'application/json',
107
+ };
108
+ // Add cache prevention headers if caching is disabled
109
+ if (this.disableCaching) {
110
+ headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
111
+ headers['Pragma'] = 'no-cache';
112
+ headers['Expires'] = '0';
113
+ }
114
+ if (requiresAuth) {
115
+ // Ensure tokens are valid before making the request
116
+ if (this.idToken || this.refreshTokenValue) {
117
+ const tokensValid = await this.ensureValidTokens();
118
+ if (!tokensValid) {
119
+ this.log('error', `[${requestId}] Tokens are not valid and could not be refreshed`, {
120
+ url,
121
+ method,
122
+ hasIdToken: !!this.idToken,
123
+ hasRefreshToken: !!this.refreshTokenValue,
124
+ tokenExpired: this.isTokenExpired(),
125
+ timestamp: new Date().toISOString()
126
+ });
127
+ throw new ApiClientError('Authentication failed - no valid tokens available', 401);
128
+ }
129
+ }
130
+ if (!this.idToken) {
131
+ this.log('error', `[${requestId}] Authentication required but no ID token available`, {
132
+ url,
133
+ method,
134
+ hasRefreshToken: !!this.refreshTokenValue,
135
+ timestamp: new Date().toISOString()
136
+ });
137
+ throw new ApiClientError('Authentication required but no token available', 401);
138
+ }
139
+ // Validate token format
140
+ if (!this.idToken.includes('.')) {
141
+ this.log('error', `[${requestId}] Invalid token format - token must be a valid JWT`, {
142
+ tokenPreview: this.idToken.substring(0, 20) + '...'
143
+ });
144
+ throw new Error('Invalid token format - token must be a valid JWT');
145
+ }
146
+ headers['Authorization'] = `${this.idToken}`;
147
+ // Log authentication details
148
+ this.log('debug', `[${requestId}] Request authenticated`, {
149
+ url,
150
+ method,
151
+ hasIdToken: !!this.idToken,
152
+ hasAccessToken: !!this.accessToken,
153
+ hasRefreshToken: !!this.refreshTokenValue,
154
+ tokenPreview: this.idToken.substring(0, 20) + '...',
155
+ authHeaderPreview: headers['Authorization'].substring(0, 30) + '...',
156
+ tokenExpired: this.isTokenExpired(),
157
+ timestamp: new Date().toISOString()
158
+ });
159
+ }
160
+ else {
161
+ // Log unauthenticated request
162
+ this.log('debug', `[${requestId}] Unauthenticated request`, {
163
+ url,
164
+ method,
165
+ timestamp: new Date().toISOString()
166
+ });
167
+ }
168
+ const config = {
169
+ method,
170
+ headers,
171
+ signal: AbortSignal.timeout(this.timeout),
172
+ };
173
+ // Add cache prevention to fetch config if caching is disabled
174
+ if (this.disableCaching) {
175
+ config.cache = 'no-store';
176
+ }
177
+ if (data && (method === 'POST' || method === 'PUT')) {
178
+ config.body = JSON.stringify(data);
179
+ }
180
+ try {
181
+ // Log request execution
182
+ this.log('debug', `[${requestId}] Executing HTTP request`, {
183
+ method,
184
+ url,
185
+ headers: Object.keys(headers),
186
+ hasBody: !!config.body,
187
+ bodySize: config.body ? (typeof config.body === 'string' ? config.body.length : 'unknown') : 0,
188
+ timeout: this.timeout,
189
+ timestamp: new Date().toISOString()
190
+ });
191
+ const response = await fetch(url, config);
192
+ const responseData = await response.json();
193
+ // Log response details
194
+ this.log('debug', `[${requestId}] Received HTTP response`, {
195
+ method,
196
+ url,
197
+ status: response.status,
198
+ statusText: response.statusText,
199
+ responseSize: JSON.stringify(responseData).length,
200
+ hasResponseData: !!responseData,
201
+ timestamp: new Date().toISOString()
202
+ });
203
+ if (!response.ok) {
204
+ // Log failed response details
205
+ this.log('warn', `[${requestId}] Request failed with error status`, {
206
+ url,
207
+ method,
208
+ status: response.status,
209
+ statusText: response.statusText,
210
+ responseData,
211
+ hasRefreshToken: !!this.refreshTokenValue,
212
+ timestamp: new Date().toISOString()
213
+ });
214
+ // Handle 401 Unauthorized with automatic token refresh
215
+ if (response.status === 401 && this.refreshTokenValue && requiresAuth) {
216
+ this.log('info', `[${requestId}] ID token expired, attempting refresh`, {
217
+ url,
218
+ method,
219
+ hasRefreshToken: !!this.refreshTokenValue,
220
+ refreshTokenPreview: this.refreshTokenValue.substring(0, 20) + '...',
221
+ timestamp: new Date().toISOString()
222
+ });
223
+ try {
224
+ const refreshResponse = await this.post('/auth/refresh', {
225
+ refreshToken: this.refreshTokenValue
226
+ }, false);
227
+ this.setAccessToken(refreshResponse.tokens.accessToken);
228
+ this.setIdToken(refreshResponse.tokens.idToken);
229
+ this.log('info', `[${requestId}] Tokens refreshed successfully, retrying request`, {
230
+ url,
231
+ method,
232
+ newTokenPreview: this.idToken?.substring(0, 20) + '...',
233
+ timestamp: new Date().toISOString()
234
+ });
235
+ // Retry the original request with new ID token
236
+ const retryHeaders = { ...headers };
237
+ retryHeaders['Authorization'] = `${this.idToken}`;
238
+ const retryConfig = {
239
+ ...config,
240
+ headers: retryHeaders
241
+ };
242
+ this.log('debug', `[${requestId}] Retrying original request with new token`, {
243
+ url,
244
+ method,
245
+ newAuthHeaderPreview: retryHeaders['Authorization'].substring(0, 30) + '...',
246
+ timestamp: new Date().toISOString()
247
+ });
248
+ const retryResponse = await fetch(url, retryConfig);
249
+ const retryData = await retryResponse.json();
250
+ if (!retryResponse.ok) {
251
+ this.log('error', `[${requestId}] Retry request failed after token refresh`, {
252
+ url,
253
+ method,
254
+ status: retryResponse.status,
255
+ statusText: retryResponse.statusText,
256
+ responseData: retryData,
257
+ timestamp: new Date().toISOString()
258
+ });
259
+ throw new ApiClientError(retryData.message || retryData.error || 'Request failed after token refresh', retryResponse.status, retryData);
260
+ }
261
+ this.log('info', `[${requestId}] Retry request successful after token refresh`, {
262
+ url,
263
+ method,
264
+ status: retryResponse.status,
265
+ responseSize: JSON.stringify(retryData).length,
266
+ timestamp: new Date().toISOString()
267
+ });
268
+ return retryData;
269
+ }
270
+ catch (refreshError) {
271
+ this.log('error', `[${requestId}] Token refresh failed`, {
272
+ url,
273
+ method,
274
+ error: refreshError instanceof Error ? refreshError.message : String(refreshError),
275
+ hasRefreshToken: !!this.refreshTokenValue,
276
+ refreshTokenPreview: this.refreshTokenValue ? this.refreshTokenValue.substring(0, 20) + '...' : 'none',
277
+ timestamp: new Date().toISOString()
278
+ });
279
+ this.clearTokens();
280
+ throw new ApiClientError('Session expired. Please log in again.', 401, {
281
+ error: 'Token refresh failed',
282
+ originalError: refreshError instanceof Error ? refreshError.message : String(refreshError),
283
+ requestId
284
+ });
285
+ }
286
+ }
287
+ // Log other error responses
288
+ this.log('error', `[${requestId}] Request failed with status ${response.status}`, {
289
+ url,
290
+ method,
291
+ status: response.status,
292
+ statusText: response.statusText,
293
+ responseData,
294
+ timestamp: new Date().toISOString()
295
+ });
296
+ throw new ApiClientError(responseData.details ? JSON.stringify(responseData.details) : responseData.message || responseData.error || 'Request failed', response.status, { ...responseData, requestId });
297
+ }
298
+ // Log successful response
299
+ this.log('debug', `[${requestId}] Request completed successfully`, {
300
+ url,
301
+ method,
302
+ status: response.status,
303
+ statusText: response.statusText,
304
+ responseSize: JSON.stringify(responseData).length,
305
+ hasResponseData: !!responseData,
306
+ timestamp: new Date().toISOString()
307
+ });
308
+ return responseData;
309
+ }
310
+ catch (error) {
311
+ if (error instanceof ApiClientError) {
312
+ throw error;
313
+ }
314
+ // Log network/request errors
315
+ this.log('error', `[${requestId}] Request error occurred`, {
316
+ url,
317
+ method,
318
+ error: error instanceof Error ? error.message : String(error),
319
+ errorName: error && typeof error === 'object' && 'name' in error ? error.name : 'Unknown',
320
+ stack: error instanceof Error ? error.stack : undefined,
321
+ timestamp: new Date().toISOString()
322
+ });
323
+ // Handle errors that have name and message properties (including custom fetch errors)
324
+ if (error && typeof error === 'object' && 'name' in error && 'message' in error) {
325
+ const errorName = error.name;
326
+ const errorMessage = error.message;
327
+ if (errorName === 'AbortError') {
328
+ throw new ApiClientError('Request timeout', 408, { requestId, url });
329
+ }
330
+ // Handle TypeError: fetch failed (common in Node.js environments)
331
+ if (errorName === 'TypeError' && errorMessage === 'fetch failed') {
332
+ // Check if there's a cause with more specific error information
333
+ const cause = error.cause;
334
+ if (cause && cause.code === 'ENOTFOUND') {
335
+ throw new ApiClientError(`DNS resolution failed: Cannot resolve hostname for ${url}`, 0, { requestId, cause: cause.code });
336
+ }
337
+ throw new ApiClientError(`Network error: Cannot connect to ${url} - check if the server is running and accessible`, 0, { requestId });
338
+ }
339
+ // Handle other TypeError cases (like DNS resolution failures)
340
+ if (errorName === 'TypeError') {
341
+ throw new ApiClientError(`Network error: ${errorMessage} for ${url}`, 0, { requestId });
342
+ }
343
+ // Handle network errors more specifically
344
+ if (errorMessage.includes('fetch')) {
345
+ throw new ApiClientError(`Network error: ${errorMessage}`, 0, { requestId });
346
+ }
347
+ if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('getaddrinfo ENOTFOUND')) {
348
+ throw new ApiClientError(`DNS resolution failed: Cannot resolve hostname for ${url}`, 0, { requestId });
349
+ }
350
+ if (errorMessage.includes('ECONNREFUSED')) {
351
+ throw new ApiClientError(`Connection refused: Server at ${url} is not accepting connections`, 0, { requestId });
352
+ }
353
+ if (errorMessage.includes('ETIMEDOUT')) {
354
+ throw new ApiClientError(`Connection timeout: Server at ${url} did not respond in time`, 0, { requestId });
355
+ }
356
+ throw new ApiClientError(`Network error: ${errorMessage}`, 0, { requestId });
357
+ }
358
+ // Fallback for standard Error instances
359
+ if (error instanceof Error) {
360
+ if (error.name === 'AbortError') {
361
+ throw new ApiClientError('Request timeout', 408);
362
+ }
363
+ // Handle TypeError: fetch failed (common in Node.js environments)
364
+ if (error.name === 'TypeError' && error.message === 'fetch failed') {
365
+ // Check if there's a cause with more specific error information
366
+ const cause = error.cause;
367
+ if (cause && cause.code === 'ENOTFOUND') {
368
+ throw new ApiClientError(`DNS resolution failed: Cannot resolve hostname for ${url}`, 0);
369
+ }
370
+ throw new ApiClientError(`Network error: Cannot connect to ${url} - check if the server is running and accessible`, 0);
371
+ }
372
+ // Handle other TypeError cases (like DNS resolution failures)
373
+ if (error.name === 'TypeError') {
374
+ throw new ApiClientError(`Network error: ${error.message} for ${url}`, 0);
375
+ }
376
+ // Handle network errors more specifically
377
+ if (error.message.includes('fetch')) {
378
+ throw new ApiClientError(`Network error: ${error.message}`, 0);
379
+ }
380
+ if (error.message.includes('ENOTFOUND') || error.message.includes('getaddrinfo ENOTFOUND')) {
381
+ throw new ApiClientError(`DNS resolution failed: Cannot resolve hostname for ${url}`, 0);
382
+ }
383
+ if (error.message.includes('ECONNREFUSED')) {
384
+ throw new ApiClientError(`Connection refused: Server at ${url} is not accepting connections`, 0);
385
+ }
386
+ if (error.message.includes('ETIMEDOUT')) {
387
+ throw new ApiClientError(`Connection timeout: Server at ${url} did not respond in time`, 0);
388
+ }
389
+ throw new ApiClientError(`Network error: ${error.message}`, 0);
390
+ }
391
+ throw new ApiClientError(`Unknown error occurred: ${String(error)}`, 0);
392
+ }
393
+ }
394
+ async get(path, requiresAuth = true) {
395
+ // Add timestamp to prevent caching if caching is disabled
396
+ if (this.disableCaching) {
397
+ const separator = path.includes('?') ? '&' : '?';
398
+ const cacheBuster = `${separator}_t=${Date.now()}`;
399
+ return this.makeRequest('GET', path + cacheBuster, undefined, requiresAuth);
400
+ }
401
+ return this.makeRequest('GET', path, undefined, requiresAuth);
402
+ }
403
+ async post(path, data, requiresAuth = true) {
404
+ return this.makeRequest('POST', path, data, requiresAuth);
405
+ }
406
+ async put(path, data, requiresAuth = true) {
407
+ return this.makeRequest('PUT', path, data, requiresAuth);
408
+ }
409
+ async delete(path, data, requiresAuth = true) {
410
+ return this.makeRequest('DELETE', path, data, requiresAuth);
411
+ }
412
+ }
413
+ // ============================================================================
414
+ // ERROR CLASS
415
+ // ============================================================================
416
+ class ApiClientError extends Error {
417
+ constructor(message, statusCode, response) {
418
+ super(message);
419
+ this.name = 'ApiClientError';
420
+ this.statusCode = statusCode;
421
+ this.response = response;
422
+ }
423
+ }
424
+ exports.ApiClientError = ApiClientError;
425
+ // ============================================================================
426
+ // MAIN API CLIENT
427
+ // ============================================================================
428
+ class ApiClient extends BaseApiClient {
429
+ // ============================================================================
430
+ // AUTHENTICATION METHODS
431
+ // ============================================================================
432
+ /**
433
+ * Authenticate user with email and password
434
+ */
435
+ async login(request) {
436
+ this.log('info', 'Starting user authentication', {
437
+ email: request.email,
438
+ hasPassword: !!request.password,
439
+ passwordLength: request.password?.length || 0,
440
+ timestamp: new Date().toISOString()
441
+ });
442
+ try {
443
+ const response = await this.post('/auth/authenticate', request, false);
444
+ this.log('info', 'Authentication successful, storing tokens', {
445
+ email: request.email,
446
+ hasAccessToken: !!response.tokens.accessToken,
447
+ hasIdToken: !!response.tokens.idToken,
448
+ hasRefreshToken: !!response.tokens.refreshToken,
449
+ accessTokenPreview: response.tokens.accessToken.substring(0, 20) + '...',
450
+ idTokenPreview: response.tokens.idToken.substring(0, 20) + '...',
451
+ refreshTokenPreview: response.tokens.refreshToken.substring(0, 20) + '...',
452
+ timestamp: new Date().toISOString()
453
+ });
454
+ this.setAccessToken(response.tokens.accessToken);
455
+ this.setIdToken(response.tokens.idToken);
456
+ this.setRefreshToken(response.tokens.refreshToken);
457
+ return response;
458
+ }
459
+ catch (error) {
460
+ this.log('error', 'Authentication failed', {
461
+ email: request.email,
462
+ error: error instanceof Error ? error.message : String(error),
463
+ timestamp: new Date().toISOString()
464
+ });
465
+ throw error;
466
+ }
467
+ }
468
+ /**
469
+ * Refresh authentication token
470
+ */
471
+ async refreshToken(request) {
472
+ this.log('info', 'Starting token refresh', {
473
+ hasRefreshToken: !!request.refreshToken,
474
+ refreshTokenPreview: request.refreshToken.substring(0, 20) + '...',
475
+ currentTokenStatus: {
476
+ hasIdToken: !!this.idToken,
477
+ hasAccessToken: !!this.accessToken,
478
+ tokenExpired: this.isTokenExpired()
479
+ },
480
+ timestamp: new Date().toISOString()
481
+ });
482
+ try {
483
+ const response = await this.post('/auth/refresh', request, false);
484
+ this.log('info', 'Token refresh successful, updating stored tokens', {
485
+ hasNewAccessToken: !!response.tokens.accessToken,
486
+ hasNewIdToken: !!response.tokens.idToken,
487
+ newAccessTokenPreview: response.tokens.accessToken.substring(0, 20) + '...',
488
+ newIdTokenPreview: response.tokens.idToken.substring(0, 20) + '...',
489
+ timestamp: new Date().toISOString()
490
+ });
491
+ this.setAccessToken(response.tokens.accessToken);
492
+ this.setIdToken(response.tokens.idToken);
493
+ // Note: refresh token is not returned on refresh, keep using the original
494
+ return response;
495
+ }
496
+ catch (error) {
497
+ this.log('error', 'Token refresh failed', {
498
+ error: error instanceof Error ? error.message : String(error),
499
+ refreshTokenPreview: request.refreshToken.substring(0, 20) + '...',
500
+ timestamp: new Date().toISOString()
501
+ });
502
+ throw error;
503
+ }
504
+ }
505
+ /**
506
+ * Logout user and clear tokens
507
+ */
508
+ async logout() {
509
+ this.log('info', 'Starting user logout', {
510
+ hasTokens: !!(this.idToken || this.accessToken || this.refreshTokenValue),
511
+ timestamp: new Date().toISOString()
512
+ });
513
+ try {
514
+ const response = await this.post('/auth/logout', {}, false);
515
+ this.clearTokens();
516
+ this.log('info', 'Logout successful, tokens cleared', {
517
+ timestamp: new Date().toISOString()
518
+ });
519
+ return response;
520
+ }
521
+ catch (error) {
522
+ this.log('warn', 'Logout request failed, clearing tokens anyway', {
523
+ error: error instanceof Error ? error.message : String(error),
524
+ timestamp: new Date().toISOString()
525
+ });
526
+ this.clearTokens();
527
+ throw error;
528
+ }
529
+ }
530
+ /**
531
+ * Get challenge session for user with temporary password
532
+ */
533
+ async getChallengeSession(request) {
534
+ return this.post('/auth/get-challenge-session', request, false);
535
+ }
536
+ /**
537
+ * Respond to NEW_PASSWORD_REQUIRED challenge
538
+ */
539
+ async forceChangePassword(request) {
540
+ const response = await this.post('/auth/force-change-password', request, false);
541
+ // If successful, update stored tokens
542
+ if (response.tokens) {
543
+ this.setAccessToken(response.tokens.accessToken);
544
+ this.setIdToken(response.tokens.idToken);
545
+ this.setRefreshToken(response.tokens.refreshToken);
546
+ }
547
+ return response;
548
+ }
549
+ // ============================================================================
550
+ // ORGANIZATION METHODS
551
+ // ============================================================================
552
+ /**
553
+ * Create a new organization
554
+ */
555
+ async createOrganization(request) {
556
+ return this.post('/organizations/createOrganization', request);
557
+ }
558
+ /**
559
+ * Get organization details
560
+ */
561
+ async getOrganization(organizationId) {
562
+ return this.get(`/organizations/${organizationId}`);
563
+ }
564
+ /**
565
+ * Update organization information
566
+ */
567
+ async updateOrganization(request) {
568
+ return this.put(`/organizations/${request.organizationId}`, request);
569
+ }
570
+ /**
571
+ * Delete an organization
572
+ */
573
+ async deleteOrganization(organizationId) {
574
+ return this.delete(`/organizations/${organizationId}`, { organizationId });
575
+ }
576
+ /**
577
+ * Get all users for an organization
578
+ */
579
+ async getOrganizationUsers(organizationId) {
580
+ return this.get(`/organizations/${organizationId}/users`);
581
+ }
582
+ /**
583
+ * Add a user to an organization
584
+ */
585
+ async addOrganizationUser(request) {
586
+ return this.post(`/organizations/${request.organizationId}/users`, request);
587
+ }
588
+ /**
589
+ * Remove a user from an organization
590
+ */
591
+ async removeOrganizationUser(request) {
592
+ return this.delete(`/organizations/${request.organizationId}/users/${request.userEmail}`, request);
593
+ }
594
+ /**
595
+ * Update a user's role in an organization
596
+ */
597
+ async updateOrganizationUserRole(request) {
598
+ return this.put(`/organizations/${request.organizationId}/users/${request.userEmail}/role`, request);
599
+ }
600
+ /**
601
+ * Adopt an orphaned organization
602
+ */
603
+ async adoptOrganization(request) {
604
+ return this.post(`/organizations/${request.organizationId}/adopt`, request);
605
+ }
606
+ /**
607
+ * Get all orphaned organizations
608
+ */
609
+ async getOrphanedOrganizations() {
610
+ return this.get('/organizations/orphaned');
611
+ }
612
+ /**
613
+ * Get all devices for an organization
614
+ */
615
+ async getOrganizationDevices(organizationId) {
616
+ return this.get(`/organizations/${organizationId}/devices`);
617
+ }
618
+ /**
619
+ * Get all applications for an organization
620
+ */
621
+ async getOrganizationApplications(organizationId) {
622
+ return this.get(`/organizations/${organizationId}/applications`);
623
+ }
624
+ // ============================================================================
625
+ // USER METHODS
626
+ // ============================================================================
627
+ /**
628
+ * Create a new user (public route for registration)
629
+ */
630
+ async createUser(request) {
631
+ return this.post('/users/createUser', request, false);
632
+ }
633
+ /**
634
+ * Get user details
635
+ */
636
+ async getUser(email) {
637
+ return this.get(`/users/${email}`);
638
+ }
639
+ /**
640
+ * Update user information
641
+ */
642
+ async updateUser(request) {
643
+ return this.put(`/users/${request.email}`, request);
644
+ }
645
+ /**
646
+ * Delete a user
647
+ */
648
+ async deleteUser(request) {
649
+ return this.delete(`/users/${request.email}`, request);
650
+ }
651
+ /**
652
+ * Get all organizations for a user
653
+ */
654
+ async getUserOrganizations(email) {
655
+ return this.get(`/users/${email}/organizations`);
656
+ }
657
+ // ============================================================================
658
+ // APPLICATION METHODS
659
+ // ============================================================================
660
+ /**
661
+ * Create a new application
662
+ */
663
+ async createApplication(request) {
664
+ return this.post('/applications/createApplication', request);
665
+ }
666
+ /**
667
+ * Get application details
668
+ */
669
+ async getApplication(applicationId) {
670
+ return this.get(`/applications/${applicationId}`);
671
+ }
672
+ /**
673
+ * Update application information
674
+ */
675
+ async updateApplication(request) {
676
+ return this.put(`/applications/${request.applicationId}`, request);
677
+ }
678
+ /**
679
+ * Delete an application
680
+ */
681
+ async deleteApplication(applicationId) {
682
+ return this.delete(`/applications/${applicationId}`);
683
+ }
684
+ // ============================================================================
685
+ // CERTIFICATE METHODS
686
+ // ============================================================================
687
+ /**
688
+ * Get all certificates for an application
689
+ */
690
+ async getCertificates(applicationId) {
691
+ return this.get(`/applications/${applicationId}/certificates`);
692
+ }
693
+ /**
694
+ * Get certificate for a specific hostname
695
+ */
696
+ async getCertificate(applicationId, hostname) {
697
+ return this.get(`/applications/${applicationId}/certificates/hostname/${encodeURIComponent(hostname)}`);
698
+ }
699
+ /**
700
+ * Refresh/renew a certificate
701
+ */
702
+ async refreshCertificate(applicationId, certificateId) {
703
+ return this.post(`/applications/${applicationId}/certificates/${certificateId}/refresh`);
704
+ }
705
+ // ============================================================================
706
+ // DEVICE METHODS
707
+ // ============================================================================
708
+ /**
709
+ * Create a new device
710
+ */
711
+ async createDevice(request) {
712
+ return this.post('/devices/createDevice', request);
713
+ }
714
+ /**
715
+ * Get device details
716
+ */
717
+ async getDevice(deviceId) {
718
+ return this.get(`/devices/${deviceId}`);
719
+ }
720
+ /**
721
+ * Update device information
722
+ */
723
+ async updateDevice(request) {
724
+ return this.put(`/devices/${request.deviceId}`, request);
725
+ }
726
+ /**
727
+ * Delete a device
728
+ */
729
+ async deleteDevice(deviceId) {
730
+ return this.delete(`/devices/${deviceId}`);
731
+ }
732
+ /**
733
+ * Create a device with an orphaned organization
734
+ */
735
+ async createDeviceWithOrphanedOrganization(request) {
736
+ return this.post('/auth/createDeviceWithOrphanedOrg', request, false);
737
+ }
738
+ /**
739
+ * Get all applications for a device
740
+ */
741
+ async getDeviceApplications(deviceId) {
742
+ return this.get(`/devices/${deviceId}/applications`);
743
+ }
744
+ /**
745
+ * Get device pool for an organization
746
+ */
747
+ async getDevicePool(organizationId) {
748
+ return this.get(`/organizations/${organizationId}/pools`);
749
+ }
750
+ // WireGuard endpoints
751
+ async postDeviceWireGuard(applicationId, deviceId, body) {
752
+ return this.post(`/applications/${applicationId}/devices/${deviceId}/wireguard`, body);
753
+ }
754
+ async getDevicePeers(applicationId, deviceId) {
755
+ return this.get(`/applications/${applicationId}/devices/${deviceId}/peers`);
756
+ }
757
+ /**
758
+ * Get device's WireGuard config from pool
759
+ */
760
+ async getPoolDeviceWireGuard(poolId, deviceId) {
761
+ try {
762
+ return await this.get(`/pools/${poolId}/devices/${deviceId}/wireguard`);
763
+ }
764
+ catch (error) {
765
+ if (error instanceof Error && 'statusCode' in error && error.statusCode === 404) {
766
+ return null;
767
+ }
768
+ throw error;
769
+ }
770
+ }
771
+ // ============================================================================
772
+ // ORGANIZATION INVITE METHODS
773
+ // ============================================================================
774
+ /**
775
+ * Create an organization invite
776
+ */
777
+ async createOrganizationInvite(request) {
778
+ return this.post(`/organizations/${request.organizationId}/invites`, request);
779
+ }
780
+ /**
781
+ * Get all invites for an organization
782
+ */
783
+ async getOrganizationInvites(organizationId, status) {
784
+ const queryParams = status ? `?status=${status}` : '';
785
+ return this.get(`/organizations/${organizationId}/invites${queryParams}`);
786
+ }
787
+ /**
788
+ * Accept an organization invite
789
+ */
790
+ async acceptInvite(request) {
791
+ return this.post(`/invites/${request.inviteId}/accept`, request);
792
+ }
793
+ /**
794
+ * Decline an organization invite
795
+ */
796
+ async declineInvite(request) {
797
+ return this.post(`/invites/${request.inviteId}/decline`, request);
798
+ }
799
+ /**
800
+ * Cancel an organization invite
801
+ */
802
+ async cancelInvite(request) {
803
+ return this.delete(`/invites/${request.inviteId}`, request);
804
+ }
805
+ /**
806
+ * Get all invites for a user
807
+ */
808
+ async getUserInvites(email, status) {
809
+ const queryParams = status ? `?status=${status}` : '';
810
+ return this.get(`/users/${email}/invites${queryParams}`);
811
+ }
812
+ // ============================================================================
813
+ // UTILITY METHODS
814
+ // ============================================================================
815
+ /**
816
+ * Test API connectivity
817
+ */
818
+ async testConnection() {
819
+ return this.get('/test', false);
820
+ }
821
+ /**
822
+ * Check if user is authenticated
823
+ */
824
+ isAuthenticated() {
825
+ return !!this.idToken;
826
+ }
827
+ /**
828
+ * Check if user has refresh token available
829
+ */
830
+ hasRefreshToken() {
831
+ return !!this.refreshTokenValue;
832
+ }
833
+ /**
834
+ * Get current access token
835
+ */
836
+ getAccessToken() {
837
+ return this.accessToken;
838
+ }
839
+ /**
840
+ * Get current ID token
841
+ */
842
+ getIdToken() {
843
+ return this.idToken;
844
+ }
845
+ /**
846
+ * Get current refresh token
847
+ */
848
+ getRefreshToken() {
849
+ return this.refreshTokenValue;
850
+ }
851
+ /**
852
+ * Validate refresh token format
853
+ */
854
+ isValidRefreshToken(token) {
855
+ if (!token || typeof token !== 'string') {
856
+ return false;
857
+ }
858
+ // Basic validation - refresh tokens should be reasonably long
859
+ // Cognito refresh tokens can be JWT tokens or opaque tokens
860
+ // Accept tokens that are at least 20 characters (some Cognito tokens can be shorter)
861
+ if (token.length < 20) {
862
+ return false;
863
+ }
864
+ // Check for basic alphanumeric pattern (allowing for base64-like characters)
865
+ // Cognito tokens may contain dots (.) if they're JWT tokens with multiple parts
866
+ // Allow dots, plus, equals, underscores, hyphens, and alphanumeric
867
+ const validPattern = /^[A-Za-z0-9+/=_.-]+$/;
868
+ return validPattern.test(token);
869
+ }
870
+ /**
871
+ * Attempt to refresh tokens if they're expired or about to expire
872
+ */
873
+ async ensureValidTokens() {
874
+ this.log('debug', 'Checking token validity', {
875
+ hasIdToken: !!this.idToken,
876
+ hasRefreshToken: !!this.refreshTokenValue,
877
+ tokenExpired: this.isTokenExpired(),
878
+ timestamp: new Date().toISOString()
879
+ });
880
+ if (!this.isTokenExpired() && this.idToken) {
881
+ this.log('debug', 'Tokens are still valid');
882
+ return true; // Tokens are still valid
883
+ }
884
+ if (!this.refreshTokenValue) {
885
+ this.log('warn', 'No refresh token available for token refresh');
886
+ return false; // No refresh token available
887
+ }
888
+ // Validate refresh token format before attempting refresh
889
+ if (!this.isValidRefreshToken(this.refreshTokenValue)) {
890
+ this.log('warn', 'Invalid refresh token format, clearing tokens', {
891
+ refreshTokenPreview: this.refreshTokenValue.substring(0, 20) + '...'
892
+ });
893
+ this.clearTokens();
894
+ return false;
895
+ }
896
+ // Prevent concurrent refresh attempts
897
+ if (this.refreshPromise) {
898
+ this.log('debug', 'Token refresh already in progress, waiting...');
899
+ return await this.refreshPromise;
900
+ }
901
+ this.log('info', 'Starting token refresh process');
902
+ this.refreshPromise = this.performTokenRefresh();
903
+ const result = await this.refreshPromise;
904
+ this.refreshPromise = null;
905
+ this.log('debug', 'Token refresh process completed', {
906
+ success: result,
907
+ timestamp: new Date().toISOString()
908
+ });
909
+ return result;
910
+ }
911
+ /**
912
+ * Perform the actual token refresh operation
913
+ */
914
+ async performTokenRefresh() {
915
+ try {
916
+ this.log('debug', 'Starting token refresh...');
917
+ const refreshResponse = await this.post('/auth/refresh', {
918
+ refreshToken: this.refreshTokenValue
919
+ }, false);
920
+ this.setAccessToken(refreshResponse.tokens.accessToken);
921
+ this.setIdToken(refreshResponse.tokens.idToken);
922
+ this.log('debug', 'Token refresh successful');
923
+ return true;
924
+ }
925
+ catch (error) {
926
+ // Clear tokens if refresh fails
927
+ this.log('warn', 'Token refresh failed, clearing tokens');
928
+ this.clearTokens();
929
+ return false;
930
+ }
931
+ }
932
+ /**
933
+ * Execute a request with automatic retry and token refresh
934
+ */
935
+ async executeWithRetry(operation, maxRetries = 3, baseDelay = 1000) {
936
+ let lastError = null;
937
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
938
+ try {
939
+ // Ensure we have valid tokens before making the request
940
+ if (!await this.ensureValidTokens()) {
941
+ throw new ApiClientError('Authentication failed - no valid tokens available', 401);
942
+ }
943
+ return await operation();
944
+ }
945
+ catch (error) {
946
+ lastError = error instanceof Error ? error : new Error(String(error));
947
+ // If it's an authentication error and we have retries left, try to refresh tokens
948
+ if (error instanceof ApiClientError && error.statusCode === 401 && attempt < maxRetries) {
949
+ this.clearTokens();
950
+ // Wait before retrying with exponential backoff
951
+ const delay = baseDelay * Math.pow(2, attempt - 1);
952
+ await new Promise(resolve => setTimeout(resolve, delay));
953
+ continue;
954
+ }
955
+ // If it's not an auth error or we're out of retries, throw the error
956
+ if (attempt === maxRetries) {
957
+ throw lastError;
958
+ }
959
+ }
960
+ }
961
+ throw lastError || new Error('Max retries exceeded');
962
+ }
963
+ // Gateway Management Methods
964
+ /**
965
+ * Create a gateway device and EC2 instance
966
+ */
967
+ async createGateway(request) {
968
+ return this.post('/gateways', request);
969
+ }
970
+ /**
971
+ * Get a specific gateway
972
+ */
973
+ async getGateway(gatewayId) {
974
+ return this.get(`/gateways/${gatewayId}`);
975
+ }
976
+ /**
977
+ * List all gateways for an organization
978
+ */
979
+ async listGateways(request) {
980
+ return this.get(`/organizations/${request.organizationId}/gateways?type=${request.type || 'gateway'}`);
981
+ }
982
+ /**
983
+ * Delete a gateway
984
+ */
985
+ async deleteGateway(request) {
986
+ return this.delete(`/gateways/${request.gatewayId}`, request);
987
+ }
988
+ /**
989
+ * Resync agent on a gateway
990
+ */
991
+ async resyncGatewayAgent(request) {
992
+ return this.post(`/gateways/${request.gatewayId}/resync`, request);
993
+ }
994
+ /**
995
+ * Get applications for a specific gateway
996
+ */
997
+ async getGatewayApplications(request) {
998
+ return this.get(`/gateways/${request.gatewayId}/applications`);
999
+ }
1000
+ // Managed Gateway Methods (admin only)
1001
+ /**
1002
+ * Create a managed gateway
1003
+ */
1004
+ async createManagedGateway(request) {
1005
+ return this.post('/admin/managed-gateways', request);
1006
+ }
1007
+ /**
1008
+ * List all managed gateways
1009
+ */
1010
+ async listManagedGateways() {
1011
+ return this.get('/admin/managed-gateways');
1012
+ }
1013
+ /**
1014
+ * Get managed gateway details
1015
+ */
1016
+ async getManagedGateway(request) {
1017
+ return this.get(`/admin/managed-gateways/${request.gatewayId}`);
1018
+ }
1019
+ /**
1020
+ * Delete a managed gateway
1021
+ */
1022
+ async deleteManagedGateway(request) {
1023
+ return this.delete(`/admin/managed-gateways/${request.gatewayId}`, request);
1024
+ }
1025
+ /**
1026
+ * Get SSH key for a managed gateway (admin only)
1027
+ */
1028
+ async getManagedGatewaySSHKey(request) {
1029
+ return this.get(`/admin/managed-gateways/${request.gatewayId}/ssh-key`);
1030
+ }
1031
+ }
1032
+ exports.ApiClient = ApiClient;
1033
+ // ============================================================================
1034
+ // FACTORY FUNCTION
1035
+ // ============================================================================
1036
+ /**
1037
+ * Create a new API client instance
1038
+ * @param baseUrl - The base URL of the API (from environment variable)
1039
+ * @param timeout - Request timeout in milliseconds (optional, defaults to 30s)
1040
+ * @param disableCaching - Whether to disable caching (optional, defaults to true)
1041
+ * @param logFunction - Optional logging function for integration with external logging systems
1042
+ * @param apiClientLogger - Optional logger instance for module-based logging
1043
+ */
1044
+ function createApiClient(baseUrl, timeout, disableCaching, logFunction, apiClientLogger) {
1045
+ return new ApiClient({
1046
+ baseUrl,
1047
+ timeout: timeout || 30000,
1048
+ disableCaching: disableCaching || false,
1049
+ logFunction,
1050
+ apiClientLogger
1051
+ });
1052
+ }
1053
+ // ============================================================================
1054
+ // DEFAULT EXPORT
1055
+ // ============================================================================
1056
+ exports.default = ApiClient;
1057
+ //# sourceMappingURL=api-client.js.map