@barndoor-ai/sdk 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.
- package/.eslintignore +8 -0
- package/.eslintrc.cjs +102 -0
- package/.github/CODEOWNERS +4 -0
- package/.github/workflows/ci.yml +57 -0
- package/.prettierignore +6 -0
- package/.prettierrc +13 -0
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/RELEASE.md +203 -0
- package/examples/README.md +92 -0
- package/examples/basic-mcp-client.js +134 -0
- package/examples/openai-integration.js +137 -0
- package/jest.config.js +16 -0
- package/openapi.yaml +681 -0
- package/package.json +87 -0
- package/rollup.config.js +63 -0
- package/scripts/dump-core-files.js +161 -0
- package/scripts/dump-typescript-only.js +150 -0
- package/src/auth/index.ts +26 -0
- package/src/auth/pkce.ts +346 -0
- package/src/auth/store.ts +809 -0
- package/src/client.ts +512 -0
- package/src/config.ts +402 -0
- package/src/exceptions/index.ts +205 -0
- package/src/http/client.ts +272 -0
- package/src/index.ts +92 -0
- package/src/logging.ts +111 -0
- package/src/models/index.ts +156 -0
- package/src/quickstart.ts +358 -0
- package/src/version.ts +41 -0
- package/test/client.test.js +381 -0
- package/test/config.test.js +202 -0
- package/test/exceptions.test.js +142 -0
- package/test/integration.test.js +147 -0
- package/test/models.test.js +177 -0
- package/test/token-management.test.js +81 -0
- package/test/token-validation.test.js +104 -0
- package/tsconfig.json +61 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for the Barndoor SDK.
|
|
3
|
+
*
|
|
4
|
+
* This module provides unified configuration that mirrors the Python SDK's
|
|
5
|
+
* configuration system, supporting environment-specific defaults and
|
|
6
|
+
* dynamic organization ID substitution.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ConfigurationError } from './exceptions';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Environment detection utilities
|
|
13
|
+
*
|
|
14
|
+
* Improved detection that works correctly under bundlers where process gets shimmed.
|
|
15
|
+
* We check for window first since that's the most reliable browser indicator.
|
|
16
|
+
*/
|
|
17
|
+
export const isBrowser: boolean =
|
|
18
|
+
typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
19
|
+
export const isNode: boolean =
|
|
20
|
+
typeof window === 'undefined' &&
|
|
21
|
+
typeof process !== 'undefined' &&
|
|
22
|
+
process.versions != null &&
|
|
23
|
+
process.versions.node != null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Browser window with optional ENV object for environment variables.
|
|
27
|
+
*/
|
|
28
|
+
declare global {
|
|
29
|
+
interface Window {
|
|
30
|
+
ENV?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration options for BarndoorConfig constructor.
|
|
36
|
+
*/
|
|
37
|
+
export interface BarndoorConfigOptions {
|
|
38
|
+
/** Auth0 domain */
|
|
39
|
+
authDomain?: string;
|
|
40
|
+
/** OAuth client ID */
|
|
41
|
+
clientId?: string;
|
|
42
|
+
/** OAuth client secret */
|
|
43
|
+
clientSecret?: string;
|
|
44
|
+
/** API audience identifier */
|
|
45
|
+
apiAudience?: string;
|
|
46
|
+
/** API base URL template */
|
|
47
|
+
apiBaseUrl?: string;
|
|
48
|
+
/** MCP base URL template */
|
|
49
|
+
mcpBaseUrl?: string;
|
|
50
|
+
/** Environment name */
|
|
51
|
+
environment?: string;
|
|
52
|
+
/** Whether to prompt for login */
|
|
53
|
+
promptForLogin?: boolean;
|
|
54
|
+
/** Whether to skip login in local environment */
|
|
55
|
+
skipLoginLocal?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get environment variable value (works in both Node.js and browser)
|
|
60
|
+
*/
|
|
61
|
+
function getEnvVar(name: string, defaultValue = ''): string {
|
|
62
|
+
if (isNode) {
|
|
63
|
+
return process.env[name] ?? defaultValue;
|
|
64
|
+
} else if (isBrowser && window.ENV) {
|
|
65
|
+
// Browser environment with injected ENV object
|
|
66
|
+
return window.ENV[name] ?? defaultValue;
|
|
67
|
+
}
|
|
68
|
+
return defaultValue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Unified configuration for the Barndoor SDK.
|
|
73
|
+
*
|
|
74
|
+
* Mirrors the Python SDK's BarndoorConfig class with environment-specific
|
|
75
|
+
* defaults and support for organization ID templating.
|
|
76
|
+
*/
|
|
77
|
+
export class BarndoorConfig {
|
|
78
|
+
/** Auth0 domain */
|
|
79
|
+
public authDomain: string;
|
|
80
|
+
/** OAuth client ID */
|
|
81
|
+
public clientId: string;
|
|
82
|
+
/** OAuth client secret */
|
|
83
|
+
public clientSecret: string;
|
|
84
|
+
/** API audience identifier */
|
|
85
|
+
public apiAudience: string;
|
|
86
|
+
/** API base URL template */
|
|
87
|
+
public apiBaseUrl: string;
|
|
88
|
+
/** MCP base URL template */
|
|
89
|
+
public mcpBaseUrl: string;
|
|
90
|
+
/** Environment name */
|
|
91
|
+
public environment: string;
|
|
92
|
+
/** Whether to prompt for login */
|
|
93
|
+
public promptForLogin: boolean;
|
|
94
|
+
/** Whether to skip login in local environment */
|
|
95
|
+
public skipLoginLocal: boolean;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create a new BarndoorConfig instance.
|
|
99
|
+
* @param options - Configuration options
|
|
100
|
+
*/
|
|
101
|
+
constructor(options: BarndoorConfigOptions = {}) {
|
|
102
|
+
// Authentication settings
|
|
103
|
+
this.authDomain = options.authDomain ?? (getEnvVar('AUTH_DOMAIN') || 'auth.barndoor.ai');
|
|
104
|
+
this.clientId = options.clientId ?? (getEnvVar('AGENT_CLIENT_ID') || '');
|
|
105
|
+
this.clientSecret = options.clientSecret ?? (getEnvVar('AGENT_CLIENT_SECRET') || '');
|
|
106
|
+
this.apiAudience = options.apiAudience ?? (getEnvVar('API_AUDIENCE') || 'https://barndoor.ai/');
|
|
107
|
+
|
|
108
|
+
// Environment settings
|
|
109
|
+
this.environment =
|
|
110
|
+
options.environment ?? (getEnvVar('MODE') || getEnvVar('BARNDOOR_ENV') || 'production');
|
|
111
|
+
|
|
112
|
+
// Runtime settings
|
|
113
|
+
this.promptForLogin = options.promptForLogin ?? false;
|
|
114
|
+
this.skipLoginLocal = options.skipLoginLocal ?? false;
|
|
115
|
+
|
|
116
|
+
// Initialize URL properties (will be set by _setEnvironmentDefaults)
|
|
117
|
+
this.apiBaseUrl = '';
|
|
118
|
+
this.mcpBaseUrl = '';
|
|
119
|
+
|
|
120
|
+
// Set environment-specific defaults
|
|
121
|
+
this._setEnvironmentDefaults(options);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Set environment-specific default URLs.
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
private _setEnvironmentDefaults(options: BarndoorConfigOptions): void {
|
|
129
|
+
const env = this.environment.toLowerCase();
|
|
130
|
+
|
|
131
|
+
if (env === 'localdev' || env === 'local') {
|
|
132
|
+
this.authDomain = this.authDomain || 'localhost:3001';
|
|
133
|
+
this.apiBaseUrl =
|
|
134
|
+
options.apiBaseUrl ?? (getEnvVar('BARNDOOR_API') || 'http://localhost:8000');
|
|
135
|
+
this.mcpBaseUrl =
|
|
136
|
+
options.mcpBaseUrl ??
|
|
137
|
+
(getEnvVar('BARNDOOR_MCP') || getEnvVar('BARNDOOR_URL') || 'http://localhost:8000');
|
|
138
|
+
} else if (env === 'development' || env === 'dev') {
|
|
139
|
+
this.apiBaseUrl =
|
|
140
|
+
options.apiBaseUrl ?? (getEnvVar('BARNDOOR_API') || 'https://api.barndoordev.com');
|
|
141
|
+
this.mcpBaseUrl =
|
|
142
|
+
options.mcpBaseUrl ??
|
|
143
|
+
(getEnvVar('BARNDOOR_MCP') ||
|
|
144
|
+
getEnvVar('BARNDOOR_URL') ||
|
|
145
|
+
'https://{organization_id}.mcp.barndoordev.com');
|
|
146
|
+
} else {
|
|
147
|
+
// production
|
|
148
|
+
this.apiBaseUrl =
|
|
149
|
+
options.apiBaseUrl ?? (getEnvVar('BARNDOOR_API') || 'https://api.barndoor.ai');
|
|
150
|
+
this.mcpBaseUrl =
|
|
151
|
+
options.mcpBaseUrl ??
|
|
152
|
+
(getEnvVar('BARNDOOR_MCP') ||
|
|
153
|
+
getEnvVar('BARNDOOR_URL') ||
|
|
154
|
+
'https://{organization_id}.mcp.barndoor.ai');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get static configuration (without organization ID substitution).
|
|
160
|
+
* @returns Static configuration instance
|
|
161
|
+
*/
|
|
162
|
+
public static getStaticConfig(): BarndoorConfig {
|
|
163
|
+
return new BarndoorConfig();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get dynamic configuration with organization ID substituted.
|
|
168
|
+
* @param jwtToken - JWT token to extract organization ID from
|
|
169
|
+
* @param options - Configuration options
|
|
170
|
+
* @returns Dynamic configuration instance
|
|
171
|
+
*/
|
|
172
|
+
public static getDynamicConfig(
|
|
173
|
+
jwtToken: string,
|
|
174
|
+
options: {
|
|
175
|
+
/** Whether to throw error for tokens without organization info */
|
|
176
|
+
requireOrganization?: boolean;
|
|
177
|
+
/** Fallback organization ID to use if none found in token */
|
|
178
|
+
fallbackOrganizationId?: string;
|
|
179
|
+
} = {}
|
|
180
|
+
): BarndoorConfig {
|
|
181
|
+
const { requireOrganization = true, fallbackOrganizationId } = options;
|
|
182
|
+
const config = new BarndoorConfig();
|
|
183
|
+
|
|
184
|
+
// Try to extract organization ID safely
|
|
185
|
+
const orgResult = extractOrganizationIdSafe(jwtToken);
|
|
186
|
+
|
|
187
|
+
if (orgResult.hasOrganization) {
|
|
188
|
+
// Organization found - validate and substitute in URLs
|
|
189
|
+
const raw = String(orgResult.organizationId ?? '')
|
|
190
|
+
.trim()
|
|
191
|
+
.toLowerCase();
|
|
192
|
+
const safe = /^[a-z0-9-]+$/.test(raw);
|
|
193
|
+
if (!safe) {
|
|
194
|
+
throw new ConfigurationError('Invalid organization ID format from token');
|
|
195
|
+
}
|
|
196
|
+
config.apiBaseUrl = config.apiBaseUrl.replace('{organization_id}', raw);
|
|
197
|
+
config.mcpBaseUrl = config.mcpBaseUrl.replace('{organization_id}', raw);
|
|
198
|
+
return config;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// No organization found - handle based on options
|
|
202
|
+
if (fallbackOrganizationId) {
|
|
203
|
+
// Use fallback organization ID
|
|
204
|
+
config.apiBaseUrl = config.apiBaseUrl.replace('{organization_id}', fallbackOrganizationId);
|
|
205
|
+
config.mcpBaseUrl = config.mcpBaseUrl.replace('{organization_id}', fallbackOrganizationId);
|
|
206
|
+
return config;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (requireOrganization) {
|
|
210
|
+
// Throw error with helpful message
|
|
211
|
+
const errorMessage = orgResult.error || 'No organization information found in token';
|
|
212
|
+
throw new ConfigurationError(
|
|
213
|
+
`Failed to extract organization ID from token: ${errorMessage}. ` +
|
|
214
|
+
'This token may be for a personal account or may be missing organization claims. ' +
|
|
215
|
+
'Consider using getStaticConfig() for organization-independent operations or ' +
|
|
216
|
+
'provide a fallbackOrganizationId in the options.'
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Return config without organization substitution (URLs will contain {organization_id} placeholder)
|
|
221
|
+
return config;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Validate the configuration.
|
|
226
|
+
* @throws ConfigurationError if configuration is invalid
|
|
227
|
+
*/
|
|
228
|
+
public validate(): void {
|
|
229
|
+
if (!this.authDomain || this.authDomain.trim() === '') {
|
|
230
|
+
throw new ConfigurationError('authDomain is required');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!this.apiAudience || this.apiAudience.trim() === '') {
|
|
234
|
+
throw new ConfigurationError('apiAudience is required');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!this.apiBaseUrl || this.apiBaseUrl.trim() === '') {
|
|
238
|
+
throw new ConfigurationError('apiBaseUrl is required');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!this.mcpBaseUrl || this.mcpBaseUrl.trim() === '') {
|
|
242
|
+
throw new ConfigurationError('mcpBaseUrl is required');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* JWT payload interface for organization extraction.
|
|
249
|
+
*/
|
|
250
|
+
interface JWTPayload {
|
|
251
|
+
user?: {
|
|
252
|
+
organization_name?: string;
|
|
253
|
+
organization_slug?: string;
|
|
254
|
+
};
|
|
255
|
+
'https://barndoor.ai/organization_slug'?: string;
|
|
256
|
+
organization_slug?: string;
|
|
257
|
+
org_slug?: string;
|
|
258
|
+
[key: string]: unknown;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Cross-platform base64 decode function.
|
|
263
|
+
* @param str - Base64 string to decode
|
|
264
|
+
* @returns Decoded string
|
|
265
|
+
*/
|
|
266
|
+
function base64Decode(str: string): string {
|
|
267
|
+
if (typeof globalThis !== 'undefined' && globalThis.atob) {
|
|
268
|
+
return globalThis.atob(str);
|
|
269
|
+
} else if (typeof Buffer !== 'undefined') {
|
|
270
|
+
return Buffer.from(str, 'base64').toString('utf-8');
|
|
271
|
+
} else {
|
|
272
|
+
throw new Error('No base64 decode function available');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Result of organization ID extraction from JWT token.
|
|
278
|
+
*/
|
|
279
|
+
interface OrganizationExtractionResult {
|
|
280
|
+
/** Organization ID if found */
|
|
281
|
+
organizationId?: string;
|
|
282
|
+
/** Whether organization ID was found */
|
|
283
|
+
hasOrganization: boolean;
|
|
284
|
+
/** Error message if extraction failed */
|
|
285
|
+
error?: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extract organization ID from JWT token with graceful fallback.
|
|
290
|
+
* @param jwtToken - JWT token
|
|
291
|
+
* @returns Organization extraction result
|
|
292
|
+
*/
|
|
293
|
+
function extractOrganizationIdSafe(jwtToken: string): OrganizationExtractionResult {
|
|
294
|
+
try {
|
|
295
|
+
const parts = jwtToken.split('.');
|
|
296
|
+
if (parts.length !== 3) {
|
|
297
|
+
return {
|
|
298
|
+
hasOrganization: false,
|
|
299
|
+
error: 'Invalid JWT format - expected 3 parts separated by dots',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let payload: JWTPayload;
|
|
304
|
+
try {
|
|
305
|
+
payload = JSON.parse(
|
|
306
|
+
base64Decode(parts[1]!.replace(/-/g, '+').replace(/_/g, '/'))
|
|
307
|
+
) as JWTPayload;
|
|
308
|
+
} catch (_parseError) {
|
|
309
|
+
return {
|
|
310
|
+
hasOrganization: false,
|
|
311
|
+
error: 'Failed to parse JWT payload - token may be corrupted',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Try multiple possible locations for organization information
|
|
316
|
+
let orgSlug: string | undefined;
|
|
317
|
+
|
|
318
|
+
// Check user object first (most common location)
|
|
319
|
+
if (payload.user && typeof payload.user === 'object') {
|
|
320
|
+
orgSlug = payload.user.organization_name ?? payload.user.organization_slug;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check custom claims and standard locations
|
|
324
|
+
if (!orgSlug) {
|
|
325
|
+
const customClaimSlug = payload['https://barndoor.ai/organization_slug'];
|
|
326
|
+
const customClaimId = payload['https://barndoor.ai/organization_id'];
|
|
327
|
+
const orgSlugClaim = payload.organization_slug;
|
|
328
|
+
const orgSlugShort = payload.org_slug;
|
|
329
|
+
const orgIdClaim = payload['org_id'];
|
|
330
|
+
const organizationIdClaim = payload['organization_id'];
|
|
331
|
+
|
|
332
|
+
orgSlug =
|
|
333
|
+
(typeof customClaimSlug === 'string' ? customClaimSlug : undefined) ??
|
|
334
|
+
(typeof customClaimId === 'string' ? customClaimId : undefined) ??
|
|
335
|
+
(typeof orgSlugClaim === 'string' ? orgSlugClaim : undefined) ??
|
|
336
|
+
(typeof orgSlugShort === 'string' ? orgSlugShort : undefined) ??
|
|
337
|
+
(typeof orgIdClaim === 'string' ? orgIdClaim : undefined) ??
|
|
338
|
+
(typeof organizationIdClaim === 'string' ? organizationIdClaim : undefined);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!orgSlug || typeof orgSlug !== 'string' || orgSlug.trim() === '') {
|
|
342
|
+
return {
|
|
343
|
+
hasOrganization: false,
|
|
344
|
+
error:
|
|
345
|
+
'No organization information found in token. This token may be for a personal account or may be missing organization claims.',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
organizationId: orgSlug.trim(),
|
|
351
|
+
hasOrganization: true,
|
|
352
|
+
};
|
|
353
|
+
} catch (error) {
|
|
354
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
355
|
+
return {
|
|
356
|
+
hasOrganization: false,
|
|
357
|
+
error: `Failed to decode JWT token: ${errorMessage}`,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Get static configuration instance.
|
|
364
|
+
* @returns Static configuration instance
|
|
365
|
+
*/
|
|
366
|
+
export function getStaticConfig(): BarndoorConfig {
|
|
367
|
+
return BarndoorConfig.getStaticConfig();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get dynamic configuration with organization ID substituted.
|
|
372
|
+
* @param jwtToken - JWT token
|
|
373
|
+
* @param options - Configuration options
|
|
374
|
+
* @returns Dynamic configuration instance
|
|
375
|
+
*/
|
|
376
|
+
export function getDynamicConfig(
|
|
377
|
+
jwtToken: string,
|
|
378
|
+
options?: {
|
|
379
|
+
requireOrganization?: boolean;
|
|
380
|
+
fallbackOrganizationId?: string;
|
|
381
|
+
}
|
|
382
|
+
): BarndoorConfig {
|
|
383
|
+
return BarndoorConfig.getDynamicConfig(jwtToken, options);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Check if a JWT token contains organization information.
|
|
388
|
+
* @param jwtToken - JWT token to check
|
|
389
|
+
* @returns Object with organization info and any error details
|
|
390
|
+
*/
|
|
391
|
+
export function checkTokenOrganization(jwtToken: string): OrganizationExtractionResult {
|
|
392
|
+
return extractOrganizationIdSafe(jwtToken);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Check if a JWT token has organization information (simple boolean check).
|
|
397
|
+
* @param jwtToken - JWT token to check
|
|
398
|
+
* @returns True if token contains organization information
|
|
399
|
+
*/
|
|
400
|
+
export function hasOrganizationInfo(jwtToken: string): boolean {
|
|
401
|
+
return extractOrganizationIdSafe(jwtToken).hasOrganization;
|
|
402
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception classes for the Barndoor SDK.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a complete hierarchy of error classes that mirror
|
|
5
|
+
* the Python SDK exceptions exactly, ensuring API compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base exception for all Barndoor SDK errors.
|
|
10
|
+
*/
|
|
11
|
+
export class BarndoorError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new BarndoorError.
|
|
14
|
+
* @param message - Error message
|
|
15
|
+
*/
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
// Avoid relying on constructor.name (can be minified in builds)
|
|
19
|
+
this.name = 'BarndoorError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Raised when authentication fails.
|
|
25
|
+
*/
|
|
26
|
+
export class AuthenticationError extends BarndoorError {
|
|
27
|
+
/** Optional error code for specific authentication failures */
|
|
28
|
+
public readonly errorCode: string | null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new AuthenticationError.
|
|
32
|
+
* @param message - Error message
|
|
33
|
+
* @param errorCode - Optional error code
|
|
34
|
+
*/
|
|
35
|
+
constructor(message: string, errorCode: string | null = null) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'AuthenticationError';
|
|
38
|
+
this.errorCode = errorCode;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Raised when token operations fail.
|
|
44
|
+
*/
|
|
45
|
+
export class TokenError extends AuthenticationError {
|
|
46
|
+
/** Optional help text for resolving the error */
|
|
47
|
+
public readonly helpText: string | null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a new TokenError.
|
|
51
|
+
* @param message - Error message
|
|
52
|
+
* @param helpText - Optional help text
|
|
53
|
+
*/
|
|
54
|
+
constructor(message: string, helpText: string | null = null) {
|
|
55
|
+
let fullMessage = message;
|
|
56
|
+
if (helpText) {
|
|
57
|
+
fullMessage += ` ${helpText}`;
|
|
58
|
+
} else {
|
|
59
|
+
fullMessage += " Run 'barndoor-login' to authenticate.";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
super(fullMessage);
|
|
63
|
+
this.name = 'TokenError';
|
|
64
|
+
this.helpText = helpText;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Raised when a token has expired.
|
|
70
|
+
*/
|
|
71
|
+
export class TokenExpiredError extends TokenError {}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Raised when token validation fails.
|
|
75
|
+
*/
|
|
76
|
+
export class TokenValidationError extends TokenError {}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Raised when unable to connect to the Barndoor API.
|
|
80
|
+
*/
|
|
81
|
+
export class ConnectionError extends BarndoorError {
|
|
82
|
+
/** The URL that failed to connect */
|
|
83
|
+
public readonly url: string;
|
|
84
|
+
/** The original error that caused the connection failure */
|
|
85
|
+
public readonly originalError: Error;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a new ConnectionError.
|
|
89
|
+
* @param url - The URL that failed to connect
|
|
90
|
+
* @param originalError - The original error that caused the failure
|
|
91
|
+
*/
|
|
92
|
+
constructor(url: string, originalError: Error) {
|
|
93
|
+
let userMessage: string;
|
|
94
|
+
const errorStr = originalError.toString().toLowerCase();
|
|
95
|
+
|
|
96
|
+
if (errorStr.includes('timeout')) {
|
|
97
|
+
userMessage = `Connection to ${url} timed out. Please check your internet connection and try again.`;
|
|
98
|
+
} else if (errorStr.includes('connection refused')) {
|
|
99
|
+
userMessage = `Could not connect to ${url}. The service may be unavailable.`;
|
|
100
|
+
} else if (errorStr.includes('name resolution') || errorStr.includes('getaddrinfo')) {
|
|
101
|
+
userMessage = `Could not resolve hostname for ${url}. Please check the URL and your DNS settings.`;
|
|
102
|
+
} else {
|
|
103
|
+
userMessage = `Failed to connect to ${url}. Please check your internet connection.`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
super(userMessage);
|
|
107
|
+
this.url = url;
|
|
108
|
+
this.originalError = originalError;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Raised for HTTP error responses.
|
|
114
|
+
*/
|
|
115
|
+
export class HTTPError extends BarndoorError {
|
|
116
|
+
/** HTTP status code */
|
|
117
|
+
public readonly statusCode: number;
|
|
118
|
+
/** Raw response body */
|
|
119
|
+
public readonly responseBody: string | null;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a new HTTPError.
|
|
123
|
+
* @param statusCode - HTTP status code
|
|
124
|
+
* @param message - Error message
|
|
125
|
+
* @param responseBody - Raw response body
|
|
126
|
+
*/
|
|
127
|
+
constructor(statusCode: number, message: string, responseBody: string | null = null) {
|
|
128
|
+
const userMessage = HTTPError._createUserFriendlyMessage(statusCode, message, responseBody);
|
|
129
|
+
super(userMessage);
|
|
130
|
+
this.name = 'HTTPError';
|
|
131
|
+
this.statusCode = statusCode;
|
|
132
|
+
this.responseBody = responseBody;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a user-friendly error message based on HTTP status code.
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
private static _createUserFriendlyMessage(
|
|
140
|
+
statusCode: number,
|
|
141
|
+
message: string,
|
|
142
|
+
_responseBody: string | null
|
|
143
|
+
): string {
|
|
144
|
+
const baseMessage = `Request failed (HTTP ${statusCode})`;
|
|
145
|
+
|
|
146
|
+
if (statusCode === 400) {
|
|
147
|
+
return `${baseMessage}: Invalid request. Please check your input parameters.`;
|
|
148
|
+
} else if (statusCode === 401) {
|
|
149
|
+
return `${baseMessage}: Authentication failed. Please check your token or re-authenticate.`;
|
|
150
|
+
} else if (statusCode === 403) {
|
|
151
|
+
return `${baseMessage}: Access denied. You don't have permission for this operation.`;
|
|
152
|
+
} else if (statusCode === 404) {
|
|
153
|
+
return `${baseMessage}: Resource not found. Please check the server ID or URL.`;
|
|
154
|
+
} else if (statusCode === 429) {
|
|
155
|
+
return `${baseMessage}: Rate limit exceeded. Please wait before making more requests.`;
|
|
156
|
+
} else if (statusCode >= 500 && statusCode < 600) {
|
|
157
|
+
return `${baseMessage}: Server error. Please try again later or contact support.`;
|
|
158
|
+
} else {
|
|
159
|
+
return `${baseMessage}: ${message}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Raised when a requested server is not found.
|
|
166
|
+
*/
|
|
167
|
+
export class ServerNotFoundError extends BarndoorError {
|
|
168
|
+
/** The server identifier that was not found */
|
|
169
|
+
public readonly serverIdentifier: string;
|
|
170
|
+
/** List of available servers, if provided */
|
|
171
|
+
public readonly availableServers: string[] | null;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Create a new ServerNotFoundError.
|
|
175
|
+
* @param serverIdentifier - The server identifier that was not found
|
|
176
|
+
* @param availableServers - Optional list of available servers
|
|
177
|
+
*/
|
|
178
|
+
constructor(serverIdentifier: string, availableServers: string[] | null = null) {
|
|
179
|
+
let message = `Server '${serverIdentifier}' not found`;
|
|
180
|
+
if (availableServers) {
|
|
181
|
+
message += `. Available servers: ${availableServers.join(', ')}`;
|
|
182
|
+
} else {
|
|
183
|
+
message += '. Use listServers() to see available servers.';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
super(message);
|
|
187
|
+
this.serverIdentifier = serverIdentifier;
|
|
188
|
+
this.availableServers = availableServers;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Raised when OAuth authentication fails.
|
|
194
|
+
*/
|
|
195
|
+
export class OAuthError extends AuthenticationError {}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Raised when there's an issue with SDK configuration.
|
|
199
|
+
*/
|
|
200
|
+
export class ConfigurationError extends BarndoorError {}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Raised when an operation times out.
|
|
204
|
+
*/
|
|
205
|
+
export class TimeoutError extends BarndoorError {}
|