Skip to content

Commit a833381

Browse files
Merge pull request #11 from Crowdhandler/improvement/error-handling
A few changes, primarily focussed on error handling improvements.
2 parents ed8265a + 5cb409c commit a833381

26 files changed

Lines changed: 1726 additions & 537 deletions

File tree

README.md

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ const { client, gatekeeper } = crowdhandler.init({
6161
// Validate the request
6262
const result = await gatekeeper.validateRequest();
6363

64+
// Check for errors first
65+
if (result.error) {
66+
console.error(`Validation error: ${result.error.message}`);
67+
// 4xx errors: promoted = false (always block access)
68+
// 5xx errors: promoted depends on trustOnFail setting
69+
}
70+
6471
// Handle the validation result
6572
if (result.setCookie) {
6673
gatekeeper.setCookie(result.cookieValue, result.domain);
@@ -192,12 +199,10 @@ try {
192199
const result = await gatekeeper.validateRequest();
193200
// ... handle result ...
194201
} catch (error) {
195-
if (error.code === 'API_CONNECTION_FAILED') {
196-
// Handle based on trustOnFail setting
197-
// true = allow access, false = use fallback room
198-
}
202+
console.error('Validation failed:', error.message);
203+
console.error('Status code:', error.statusCode);
204+
// Handle based on trustOnFail setting
199205
}
200-
```
201206

202207
### gatekeeper.setCookie(value, domain?)
203208

@@ -418,27 +423,46 @@ Full API documentation with request/response examples is available in your [Crow
418423
419424
## Error Handling
420425
421-
All SDK errors are instances of `CrowdHandlerError` with helpful context:
426+
All SDK errors are instances of `CrowdHandlerError` with consistent structure:
422427
423428
```javascript
424429
try {
425430
const rooms = await client.rooms().get();
426431
} catch (error) {
427-
console.error(error.code); // 'ROOM_NOT_FOUND'
428-
console.error(error.message); // Human-readable message
429-
console.error(error.suggestion); // Helpful next steps
430-
console.error(error.statusCode); // HTTP status code
432+
console.error(error.message); // The actual API error message
433+
console.error(error.statusCode); // HTTP status code (e.g., 401, 404)
434+
console.error(error.suggestion); // Helpful guidance for resolution
435+
console.error(error.code); // Error code for programmatic handling
431436
}
432437
```
433438
434-
### Error Codes
439+
### API Error Transparency
440+
441+
The SDK preserves the exact error messages from the CrowdHandler API, allowing you to handle specific error scenarios:
442+
443+
```javascript
444+
try {
445+
const result = await gatekeeper.validateRequest({
446+
custom: { code: 'user-code' }
447+
});
448+
} catch (error) {
449+
// Check the specific error message from the API
450+
if (error.message.includes('Invalid priority code')) {
451+
// Handle invalid access code
452+
}
453+
454+
// For advanced debugging, access the full API response
455+
const apiResponse = error.context?.apiResponse;
456+
}
457+
```
458+
459+
### Common Error Codes
435460
436461
- `INVALID_CONFIG` - Invalid SDK configuration
437462
- `MISSING_PRIVATE_KEY` - Private key required for this operation
438463
- `API_CONNECTION_FAILED` - Cannot reach CrowdHandler API
439-
- `RESOURCE_NOT_FOUND` - Requested resource doesn't exist
440-
- `RATE_LIMITED` - Too many requests
441-
- Plus more specific error codes
464+
- `API_INVALID_RESPONSE` - API returned an error
465+
- `RATE_LIMITED` - Too many requests (includes retry-after)
442466
443467
## Integration Examples
444468
@@ -468,6 +492,13 @@ async function protectRoute(req, res, next) {
468492

469493
const result = await gatekeeper.validateRequest();
470494

495+
// Check if there was an error during validation
496+
if (result.error) {
497+
console.error(`API Error ${result.error.statusCode}: ${result.error.message}`);
498+
// 4xx errors (e.g., invalid key, bad request): promoted = false (user blocked)
499+
// 5xx errors (e.g., server error): promoted based on trustOnFail setting
500+
}
501+
471502
if (result.setCookie) {
472503
gatekeeper.setCookie(result.cookieValue, result.domain);
473504
}
@@ -484,8 +515,10 @@ async function protectRoute(req, res, next) {
484515
res.locals.gatekeeper = gatekeeper;
485516
next();
486517
} catch (error) {
487-
console.error('CrowdHandler error:', error);
488-
// Decide whether to block or allow on error
518+
// This catches unexpected errors (e.g., network issues, config errors)
519+
console.error('CrowdHandler SDK error:', error.message);
520+
// trustOnFail: true (default) = allow access on error
521+
// trustOnFail: false = block access on error
489522
next();
490523
}
491524
}
@@ -607,6 +640,36 @@ await gatekeeper.recordPerformance({
607640
});
608641
```
609642
643+
### Test Error Simulation
644+
645+
For testing your error handling without making real API calls, you can simulate errors:
646+
647+
```javascript
648+
const { gatekeeper } = init({
649+
publicKey: 'YOUR_PUBLIC_KEY',
650+
request: req,
651+
response: res,
652+
options: {
653+
testError: {
654+
statusCode: 500, // Simulate a 500 error
655+
message: 'Simulated server error for testing'
656+
}
657+
}
658+
});
659+
660+
// This will return immediately with a simulated error
661+
const result = await gatekeeper.validateRequest();
662+
// result.error will contain the test error
663+
// result.promoted will be true (with default trustOnFail: true)
664+
```
665+
666+
This is useful for:
667+
- Testing error handling logic in development
668+
- Verifying fallback behavior for different error types
669+
- Integration testing without affecting production metrics
670+
671+
Note: 4xx test errors will always set `promoted: false`, while 5xx test errors respect your `trustOnFail` setting.
672+
610673
### Lite Validator Mode
611674
612675
Lite validator mode provides token refresh without API calls by checking room configuration locally. To enable it:

dist/client/base_client.js

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,31 @@ var BaseClient = /** @class */ (function () {
9191
stack: error.stack
9292
});
9393
};
94+
/**
95+
* Provides generic suggestion based on HTTP status code
96+
*/
97+
BaseClient.prototype.getGenericSuggestion = function (status) {
98+
switch (status) {
99+
case 400: return 'Check your request parameters';
100+
case 401: return 'Check your authentication credentials';
101+
case 403: return 'You do not have permission for this action';
102+
case 404: return 'The requested resource was not found';
103+
case 429: return 'Too many requests - please slow down';
104+
case 500:
105+
case 502:
106+
case 503:
107+
case 504:
108+
return 'Server error - please try again later';
109+
default:
110+
return status >= 500
111+
? 'This appears to be a server error. Please try again later or contact support@crowdhandler.com'
112+
: 'Please check your request parameters and try again';
113+
}
114+
};
94115
BaseClient.prototype.errorHandler = function (error) {
95116
var _a, _b, _c, _d;
96117
return __awaiter(this, void 0, void 0, function () {
97-
var status_1, data, retryAfter, urlMatch, resourceType, resourceId, errorMessage;
118+
var status_1, data, errorMessage, retryAfter;
98119
return __generator(this, function (_e) {
99120
// If it's already a CrowdHandlerError, just re-throw it
100121
if (error instanceof errors_1.CrowdHandlerError) {
@@ -105,28 +126,24 @@ var BaseClient = /** @class */ (function () {
105126
data = error.response.data;
106127
(0, logger_1.logger)(this.debug, "error", "API Error - Status: ".concat(status_1, " - ").concat(JSON.stringify(data)));
107128
(0, logger_1.logger)(this.debug, "error", "Response headers: ".concat(JSON.stringify(error.response.headers)));
108-
// Handle specific HTTP status codes
109-
if (status_1 === 401) {
110-
throw errors_1.createError.invalidApiKey('public');
111-
}
129+
errorMessage = (data === null || data === void 0 ? void 0 : data.error) || (data === null || data === void 0 ? void 0 : data.message) || "API request failed with status ".concat(status_1);
130+
// Special handling for rate limiting to include retry-after
112131
if (status_1 === 429) {
113132
retryAfter = error.response.headers['retry-after'];
114-
throw errors_1.createError.rateLimited(retryAfter);
115-
}
116-
if (status_1 === 404) {
117-
urlMatch = (_b = (_a = error.config) === null || _a === void 0 ? void 0 : _a.url) === null || _b === void 0 ? void 0 : _b.match(/\/v1\/(\w+)\/(\w+)/);
118-
if (urlMatch) {
119-
resourceType = urlMatch[1], resourceId = urlMatch[2];
120-
throw errors_1.createError.resourceNotFound(resourceType, resourceId);
121-
}
133+
throw new errors_1.CrowdHandlerError(errors_1.ErrorCodes.RATE_LIMITED, errorMessage, retryAfter
134+
? "Wait ".concat(retryAfter, " seconds before retrying")
135+
: 'Reduce the frequency of API calls', status_1, {
136+
url: (_a = error.config) === null || _a === void 0 ? void 0 : _a.url,
137+
method: (_b = error.config) === null || _b === void 0 ? void 0 : _b.method,
138+
apiResponse: data,
139+
retryAfter: retryAfter
140+
});
122141
}
123-
errorMessage = (data === null || data === void 0 ? void 0 : data.error) || (data === null || data === void 0 ? void 0 : data.message) || "API request failed with status ".concat(status_1);
124-
throw new errors_1.CrowdHandlerError(errors_1.ErrorCodes.API_INVALID_RESPONSE, errorMessage, status_1 >= 500
125-
? 'This appears to be a server error. Please try again later or contact support@crowdhandler.com'
126-
: 'Please check your request parameters and try again', status_1, {
142+
// Pass through the API error with full response data
143+
throw new errors_1.CrowdHandlerError(errors_1.ErrorCodes.API_INVALID_RESPONSE, errorMessage, this.getGenericSuggestion(status_1), status_1, {
127144
url: (_c = error.config) === null || _c === void 0 ? void 0 : _c.url,
128145
method: (_d = error.config) === null || _d === void 0 ? void 0 : _d.method,
129-
responseData: JSON.stringify(data).substring(0, 200)
146+
apiResponse: data // Full API response, not truncated
130147
});
131148
}
132149
else if (error.request) {

dist/common/types.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ exports.GatekeeperOptions = zod_1.z.object({
2323
liteValidator: zod_1.z.boolean().optional(),
2424
roomsConfig: exports.RoomsConfig.optional(),
2525
waitingRoom: zod_1.z.boolean().optional(),
26+
testError: zod_1.z.object({
27+
statusCode: zod_1.z.number(),
28+
message: zod_1.z.string().optional(),
29+
}).optional(), // For testing error handling
2630
});
2731
exports.GatekeeperKeyPair = zod_1.z.object({
2832
publicKey: zod_1.z.string(),
@@ -146,6 +150,11 @@ exports.ValidateRequestObject = zod_1.z.object({
146150
liteValidatorRedirect: zod_1.z.boolean().optional(),
147151
liteValidatorUrl: zod_1.z.string().optional(),
148152
domain: zod_1.z.string().optional(),
153+
error: zod_1.z.object({
154+
message: zod_1.z.string(),
155+
statusCode: zod_1.z.number().optional(),
156+
code: zod_1.z.string().optional(),
157+
}).optional(),
149158
});
150159
exports.HttpErrorWrapper = zod_1.z.object({
151160
result: zod_1.z.object({

0 commit comments

Comments
 (0)