8e5bdcc781
A `subscriptionRemoved` lifecycle notification was routed to `renewSubscription`, which PATCHes a subscription Microsoft has already deleted and always 404s ([TWENTY-SERVER-J1N](https://twenty-v7.sentry.io/issues/7604034376/), 884 events). Every sampled webhook event on that issue was `subscriptionRemoved`. Each lifecycle event now gets its own path: `subscriptionRemoved` recreates and resyncs the gap, `reauthorizationRequired` renews in place, `missed` resyncs, unrecognised events are logged and ignored. Provider errors are parsed into driver exception codes following the message-import drivers. Max retry for the renewal cron is deliberately left out and will follow separately. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23707?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: neo773 <huzef@twenty.com>
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { type MessageDescriptor } from '@lingui/core';
|
|
import { msg } from '@lingui/core/macro';
|
|
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
|
|
|
import { CustomException } from 'src/utils/custom-exception';
|
|
|
|
export enum WebhookSubscriptionDriverExceptionCode {
|
|
PROVIDER_NOT_CONFIGURED = 'PROVIDER_NOT_CONFIGURED',
|
|
PROVIDER_RESPONSE_INVALID = 'PROVIDER_RESPONSE_INVALID',
|
|
UNSUPPORTED_PROVIDER = 'UNSUPPORTED_PROVIDER',
|
|
NOT_FOUND = 'NOT_FOUND',
|
|
INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',
|
|
TEMPORARY_ERROR = 'TEMPORARY_ERROR',
|
|
UNKNOWN = 'UNKNOWN',
|
|
}
|
|
|
|
const getWebhookSubscriptionDriverExceptionUserFriendlyMessage = (
|
|
code: WebhookSubscriptionDriverExceptionCode,
|
|
) => {
|
|
switch (code) {
|
|
case WebhookSubscriptionDriverExceptionCode.NOT_FOUND:
|
|
return msg`The subscription is no longer available on the provider.`;
|
|
case WebhookSubscriptionDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
|
return msg`The provider denied access to this account. Please reconnect it.`;
|
|
case WebhookSubscriptionDriverExceptionCode.TEMPORARY_ERROR:
|
|
return msg`The provider is temporarily unavailable. Please try again later.`;
|
|
case WebhookSubscriptionDriverExceptionCode.PROVIDER_NOT_CONFIGURED:
|
|
case WebhookSubscriptionDriverExceptionCode.PROVIDER_RESPONSE_INVALID:
|
|
case WebhookSubscriptionDriverExceptionCode.UNSUPPORTED_PROVIDER:
|
|
case WebhookSubscriptionDriverExceptionCode.UNKNOWN:
|
|
return msg`The webhook subscription could not be managed for this account.`;
|
|
default:
|
|
assertUnreachable(code);
|
|
}
|
|
};
|
|
|
|
export class WebhookSubscriptionDriverException extends CustomException<WebhookSubscriptionDriverExceptionCode> {
|
|
cause?: unknown;
|
|
|
|
constructor(
|
|
message: string,
|
|
code: WebhookSubscriptionDriverExceptionCode,
|
|
{
|
|
userFriendlyMessage,
|
|
cause,
|
|
}: { userFriendlyMessage?: MessageDescriptor; cause?: unknown } = {},
|
|
) {
|
|
super(message, code, {
|
|
userFriendlyMessage:
|
|
userFriendlyMessage ??
|
|
getWebhookSubscriptionDriverExceptionUserFriendlyMessage(code),
|
|
});
|
|
|
|
if (isDefined(cause)) {
|
|
this.cause = cause;
|
|
}
|
|
}
|
|
}
|