Spaces:
Runtime error
Runtime error
| import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; | |
| import { | |
| IsString, | |
| IsUrl, | |
| IsArray, | |
| IsOptional, | |
| IsBoolean, | |
| IsInt, | |
| Min, | |
| Max, | |
| MaxLength, | |
| ArrayMinSize, | |
| IsIn, | |
| } from 'class-validator'; | |
| import { Expose, plainToInstance } from 'class-transformer'; | |
| import { Webhook } from '../entities/webhook.entity'; | |
| import { WebhookFilters } from '../filters/filter-types'; | |
| import { IsValidWebhookFilters } from '../filters/filter-validation'; | |
| import { IsHeaderMap } from './is-header-map.validator'; | |
| import { ToStrictBoolean, ToStrictNumber } from '../../../common/utils/strict-boolean'; | |
| const FILTERS_API_DESCRIPTION = | |
| 'Optional smart pre-filter. When set, every condition must match (AND) for the webhook to fire. Omit or null to fire on every subscribed event.'; | |
| const FILTERS_API_EXAMPLE = { | |
| conditions: [ | |
| { field: 'sender', operator: 'is', value: ['1234567890@c.us'] }, | |
| { field: 'body', operator: 'contains', value: 'invoice' }, | |
| ], | |
| }; | |
| // Reserved: valid webhook subscription targets that are declared but have no engine emit | |
| // source yet. Currently EMPTY — the former occupants (group.join/leave/update) are now | |
| // dispatched by both engines. Kept as a named export so the catalog/emitter drift guard | |
| // can whitelist any future intentionally-undeployed event without changing its imports. | |
| export const WEBHOOK_RESERVED_EVENTS = [] as const; | |
| export const WEBHOOK_EVENTS = [ | |
| 'message.received', | |
| 'message.sent', | |
| 'message.ack', | |
| 'message.failed', | |
| 'message.revoked', | |
| 'message.reaction', | |
| 'message.edited', | |
| 'session.status', | |
| 'session.qr', | |
| 'session.authenticated', | |
| 'session.disconnected', | |
| 'session.reconnect_loop', | |
| 'group.join', | |
| 'group.leave', | |
| 'group.update', | |
| 'call.received', | |
| ...WEBHOOK_RESERVED_EVENTS, | |
| ] as const; | |
| export type WebhookEventType = (typeof WEBHOOK_EVENTS)[number]; | |
| export class CreateWebhookDto { | |
| ({ | |
| description: 'Webhook URL to receive events', | |
| example: 'https://your-server.com/webhook', | |
| }) | |
| // require_tld:false allows hostnames without a dot (e.g. http://localhost:3000); the SSRF | |
| // guard still decides whether the host is actually allowed to be delivered to. | |
| ({ require_tld: false }) | |
| url: string; | |
| ({ | |
| description: "Event types to subscribe to. '*' subscribes to all events.", | |
| example: ['message.received', 'session.status'], | |
| enum: [...WEBHOOK_EVENTS, '*'], | |
| type: String, | |
| isArray: true, | |
| minItems: 1, | |
| }) | |
| () | |
| () | |
| (1) | |
| // Must include '*' (wildcard subscribe-all) alongside the known events. | |
| ([...WEBHOOK_EVENTS, '*'], { each: true }) | |
| events?: string[]; | |
| ({ | |
| description: 'Secret key for HMAC signature verification', | |
| example: 'your-secret-key', | |
| }) | |
| () | |
| () | |
| (255) | |
| secret?: string; | |
| ({ | |
| description: 'Custom headers to include in webhook requests', | |
| example: { 'X-Custom-Header': 'value' }, | |
| }) | |
| () | |
| () | |
| headers?: Record<string, string>; | |
| ({ description: FILTERS_API_DESCRIPTION, example: FILTERS_API_EXAMPLE }) | |
| () | |
| () | |
| filters?: WebhookFilters | null; | |
| ({ | |
| description: 'Number of retry attempts on failure', | |
| example: 3, | |
| minimum: 0, | |
| maximum: 5, | |
| }) | |
| () | |
| () | |
| () | |
| (0) | |
| (5) | |
| retryCount?: number; | |
| } | |
| export class UpdateWebhookDto { | |
| ({ description: 'Webhook URL' }) | |
| () | |
| ({ require_tld: false }) | |
| url?: string; | |
| ({ | |
| description: "Event types to subscribe to. '*' subscribes to all events.", | |
| enum: [...WEBHOOK_EVENTS, '*'], | |
| type: String, | |
| isArray: true, | |
| minItems: 1, | |
| }) | |
| () | |
| () | |
| (1) | |
| ([...WEBHOOK_EVENTS, '*'], { each: true }) | |
| events?: string[]; | |
| ({ description: 'Secret key for HMAC signature' }) | |
| () | |
| () | |
| (255) | |
| secret?: string; | |
| ({ description: 'Custom headers' }) | |
| () | |
| () | |
| headers?: Record<string, string>; | |
| ({ description: FILTERS_API_DESCRIPTION, example: FILTERS_API_EXAMPLE }) | |
| () | |
| () | |
| filters?: WebhookFilters | null; | |
| ({ description: 'Enable/disable webhook' }) | |
| () | |
| () | |
| () | |
| active?: boolean; | |
| ({ description: 'Retry count' }) | |
| () | |
| () | |
| () | |
| (0) | |
| (5) | |
| retryCount?: number; | |
| } | |
| /** | |
| * Public response shape for a webhook. Deliberately omits `secret` (the HMAC | |
| * signing key) and `headers` (which may carry receiver credentials) — these are | |
| * write-only and must never appear in any API response. | |
| * | |
| * `@Expose()` is required on every field: `fromEntity` maps with | |
| * `excludeExtraneousValues: true`, so only exposed fields are serialized and any | |
| * undeclared entity field (secret, headers, the session relation) is dropped. | |
| */ | |
| export class WebhookResponseDto { | |
| () | |
| () | |
| id: string; | |
| () | |
| () | |
| sessionId: string; | |
| () | |
| () | |
| url: string; | |
| () | |
| () | |
| events: string[]; | |
| () | |
| ({ description: FILTERS_API_DESCRIPTION, example: FILTERS_API_EXAMPLE }) | |
| filters?: WebhookFilters | null; | |
| () | |
| () | |
| active: boolean; | |
| () | |
| () | |
| retryCount: number; | |
| () | |
| () | |
| lastTriggeredAt?: Date | null; | |
| () | |
| () | |
| createdAt: Date; | |
| () | |
| () | |
| updatedAt: Date; | |
| static fromEntity(entity: Webhook): WebhookResponseDto { | |
| return plainToInstance(WebhookResponseDto, entity, { excludeExtraneousValues: true }); | |
| } | |
| static fromEntities(entities: Webhook[]): WebhookResponseDto[] { | |
| return entities.map(entity => WebhookResponseDto.fromEntity(entity)); | |
| } | |
| } | |