backend: enhance test webhook url error handling
This commit is contained in:
parent
2bea178e76
commit
66d0014c7e
@ -55,7 +55,6 @@ export const testWebhook = async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
await webhookService.testWebhookUrl(url);
|
await webhookService.testWebhookUrl(url);
|
||||||
logger.info(`Webhook URL '${url}' is valid`);
|
logger.info(`Webhook URL '${url}' is valid`);
|
||||||
// If the URL is valid, we can return a success response
|
|
||||||
return res.status(200).json({ message: 'Webhook URL is valid' });
|
return res.status(200).json({ message: 'Webhook URL is valid' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(res, error, 'testing webhook URL');
|
handleError(res, error, 'testing webhook URL');
|
||||||
|
|||||||
@ -63,8 +63,16 @@ export const errorAzureNotAvailable = (error: any): OpenViduMeetError => {
|
|||||||
return new OpenViduMeetError('ABS Error', `Azure Blob Storage is not available ${error}`, 503);
|
return new OpenViduMeetError('ABS Error', `Azure Blob Storage is not available ${error}`, 503);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const errorWebhookUrlUnreachable = (url: string): OpenViduMeetError => {
|
export const errorInvalidWebhookUrl = (url: string, reason: string): OpenViduMeetError => {
|
||||||
return new OpenViduMeetError('Webhook Error', `Webhook URL '${url}' is unreachable`, 400);
|
return new OpenViduMeetError('Webhook Error', `Webhook URL '${url}' is invalid: ${reason}`, 400);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const errorApiKeyNotConfiguredForWebhooks = (): OpenViduMeetError => {
|
||||||
|
return new OpenViduMeetError(
|
||||||
|
'Webhook Error',
|
||||||
|
'There are no API keys configured yet. Please, create one to use webhooks.',
|
||||||
|
400
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auth errors
|
// Auth errors
|
||||||
|
|||||||
@ -9,7 +9,11 @@ import {
|
|||||||
} from '@typings-ce';
|
} from '@typings-ce';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { errorWebhookUrlUnreachable } from '../models/error.model.js';
|
import {
|
||||||
|
errorApiKeyNotConfiguredForWebhooks,
|
||||||
|
errorInvalidWebhookUrl,
|
||||||
|
OpenViduMeetError
|
||||||
|
} from '../models/error.model.js';
|
||||||
import { AuthService, LoggerService, MeetStorageService } from './index.js';
|
import { AuthService, LoggerService, MeetStorageService } from './index.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -112,16 +116,20 @@ export class OpenViduWebhookService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.sendRequest(url, {
|
const signature = await this.generateWebhookSignature(creationDate, data);
|
||||||
|
|
||||||
|
await this.sendTestRequest(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'X-Timestamp': creationDate.toString(),
|
||||||
|
'X-Signature': signature
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error testing webhook URL ${url}: ${error}`);
|
this.logger.error(`Error sending test webhook to URL '${url}': ${error}`);
|
||||||
throw errorWebhookUrlUnreachable(url);
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +215,80 @@ export class OpenViduWebhookService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a test request to a webhook URL with specific error handling for testing purposes.
|
||||||
|
*
|
||||||
|
* @param url - The webhook URL to test
|
||||||
|
* @param options - Request options
|
||||||
|
*/
|
||||||
|
protected async sendTestRequest(url: string, options: RequestInit): Promise<void> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const reason =
|
||||||
|
response.status >= 500
|
||||||
|
? `Server error (${response.status} ${response.statusText})`
|
||||||
|
: response.status >= 400
|
||||||
|
? `Client error (${response.status} ${response.statusText})`
|
||||||
|
: `Unexpected response (${response.status})`;
|
||||||
|
|
||||||
|
throw errorInvalidWebhookUrl(url, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success case
|
||||||
|
this.logger.info(`Webhook test successful for URL: ${url}`);
|
||||||
|
} catch (error: any) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
// If it's already our webhook error, re-throw it
|
||||||
|
if (error instanceof OpenViduMeetError && error.name === 'Webhook Error') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle specific error types
|
||||||
|
let reason: string;
|
||||||
|
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
reason = 'Request timed out after 10 seconds';
|
||||||
|
} else if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||||
|
// Network errors
|
||||||
|
const errorCode = error.cause?.code;
|
||||||
|
|
||||||
|
switch (errorCode) {
|
||||||
|
case 'ENOTFOUND':
|
||||||
|
reason = 'Domain name could not be resolved';
|
||||||
|
break;
|
||||||
|
case 'ECONNREFUSED':
|
||||||
|
reason = 'Connection refused by server';
|
||||||
|
break;
|
||||||
|
case 'ECONNRESET':
|
||||||
|
reason = 'Connection reset by server';
|
||||||
|
break;
|
||||||
|
case 'CERT_HAS_EXPIRED':
|
||||||
|
case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE':
|
||||||
|
case 'SELF_SIGNED_CERT_IN_CHAIN':
|
||||||
|
reason = 'SSL/TLS certificate error';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reason = `Network error: ${error.message}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reason = `Connection failed: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw errorInvalidWebhookUrl(url, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected async getWebhookPreferences(): Promise<WebhookPreferences> {
|
protected async getWebhookPreferences(): Promise<WebhookPreferences> {
|
||||||
try {
|
try {
|
||||||
const { webhooksPreferences } = await this.globalPrefService.getGlobalPreferences();
|
const { webhooksPreferences } = await this.globalPrefService.getGlobalPreferences();
|
||||||
@ -228,7 +310,7 @@ export class OpenViduWebhookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (apiKeys.length === 0) {
|
if (apiKeys.length === 0) {
|
||||||
throw new Error('There are no API keys configured yet. Please, create one to use webhooks.');
|
throw errorApiKeyNotConfiguredForWebhooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the first API key
|
// Return the first API key
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user