diff --git a/api/package-lock.json b/api/package-lock.json index 404f3eafa..cd14a1767 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -35,7 +35,6 @@ "dotenv": "^16.3.1", "ejs": "^3.1.9", "express-session": "^1.17.3", - "joi": "^17.11.0", "module-alias": "^2.2.3", "mongoose": "^8.0.0", "mongoose-lean-defaults": "^2.2.1", @@ -3646,19 +3645,6 @@ "@nestjs/core": "^10.x" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -5329,24 +5315,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -13186,18 +13154,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/joi": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", - "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", diff --git a/api/package.json b/api/package.json index 2cd82dbe3..6ffcd5ded 100644 --- a/api/package.json +++ b/api/package.json @@ -70,7 +70,6 @@ "dotenv": "^16.3.1", "ejs": "^3.1.9", "express-session": "^1.17.3", - "joi": "^17.11.0", "module-alias": "^2.2.3", "mongoose": "^8.0.0", "mongoose-lean-defaults": "^2.2.1", diff --git a/api/src/chat/schemas/types/message.ts b/api/src/chat/schemas/types/message.ts index c8c8e14f4..451d7db42 100644 --- a/api/src/chat/schemas/types/message.ts +++ b/api/src/chat/schemas/types/message.ts @@ -6,14 +6,24 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { z } from 'zod'; + import { PluginName } from '@/plugins/types'; import { Message } from '../message.schema'; -import { AttachmentPayload } from './attachment'; -import { Button } from './button'; -import { ContentOptions } from './options'; -import { StdQuickReply } from './quick-reply'; +import { attachmentPayloadSchema } from './attachment'; +import { buttonSchema } from './button'; +import { contentOptionsSchema } from './options'; +import { QuickReplyType, stdQuickReplySchema } from './quick-reply'; /** * StdEventType enum is declared, and currently not used @@ -41,6 +51,10 @@ export enum IncomingMessageType { unknown = '', } +export const incomingMessageType = z.nativeEnum(IncomingMessageType); + +export type IncomingMessageTypeLiteral = z.infer; + export enum OutgoingMessageFormat { text = 'text', quickReplies = 'quickReplies', @@ -50,6 +64,12 @@ export enum OutgoingMessageFormat { carousel = 'carousel', } +export const outgoingMessageFormatSchema = z.nativeEnum(OutgoingMessageFormat); + +export type OutgoingMessageFormatLiteral = z.infer< + typeof outgoingMessageFormatSchema +>; + /** * FileType enum is declared, and currently not used **/ @@ -61,6 +81,10 @@ export enum FileType { unknown = 'unknown', } +export const fileTypeSchema = z.nativeEnum(FileType); + +export type FileTypeLiteral = z.infer; + export enum PayloadType { location = 'location', attachments = 'attachments', @@ -68,85 +92,151 @@ export enum PayloadType { button = 'button', } -export type StdOutgoingTextMessage = { text: string }; +export const payloadTypeSchema = z.nativeEnum(PayloadType); -export type StdOutgoingQuickRepliesMessage = { - text: string; - quickReplies: StdQuickReply[]; -}; +export type PayloadTypeLiteral = z.infer; -export type StdOutgoingButtonsMessage = { - text: string; - buttons: Button[]; -}; +export const stdOutgoingTextMessageSchema = z.object({ + text: z.string(), +}); -export type ContentElement = { id: string; title: string } & Record< - string, - any +export type StdOutgoingTextMessage = z.infer< + typeof stdOutgoingTextMessageSchema >; -export type StdOutgoingListMessage = { - options: ContentOptions; - elements: ContentElement[]; - pagination: { - total: number; - skip: number; - limit: number; - }; -}; - -export type StdOutgoingAttachmentMessage = { - // Stored in DB as `AttachmentPayload`, `Attachment` when populated for channels relaying - attachment: AttachmentPayload; - quickReplies?: StdQuickReply[]; -}; - -export type StdPluginMessage = { - plugin: PluginName; - args: { [key: string]: any }; -}; - -export type BlockMessage = - | string[] - | StdOutgoingTextMessage - | StdOutgoingQuickRepliesMessage - | StdOutgoingButtonsMessage - | StdOutgoingListMessage - | StdOutgoingAttachmentMessage - | StdPluginMessage; - -export type StdOutgoingMessage = - | StdOutgoingTextMessage - | StdOutgoingQuickRepliesMessage - | StdOutgoingButtonsMessage - | StdOutgoingListMessage - | StdOutgoingAttachmentMessage; - -type StdIncomingTextMessage = { text: string }; - -export type StdIncomingPostBackMessage = StdIncomingTextMessage & { - postback: string; -}; - -export type StdIncomingLocationMessage = { - type: PayloadType.location; - coordinates: { - lat: number; - lon: number; - }; -}; - -export type StdIncomingAttachmentMessage = { - type: PayloadType.attachments; - serialized_text: string; - attachment: AttachmentPayload | AttachmentPayload[]; -}; - -export type StdIncomingMessage = - | StdIncomingTextMessage - | StdIncomingPostBackMessage - | StdIncomingLocationMessage - | StdIncomingAttachmentMessage; +export const stdOutgoingQuickRepliesMessageSchema = z.object({ + text: z.string(), + quickReplies: z.array(stdQuickReplySchema), +}); + +export type StdOutgoingQuickRepliesMessage = z.infer< + typeof stdOutgoingQuickRepliesMessageSchema +>; + +export const stdOutgoingButtonsMessageSchema = z.object({ + text: z.string(), + buttons: z.array(buttonSchema), +}); + +export type StdOutgoingButtonsMessage = z.infer< + typeof stdOutgoingButtonsMessageSchema +>; + +export const contentElementSchema = z + .object({ + id: z.string(), + title: z.string(), + }) + .catchall(z.any()); + +export type ContentElement = z.infer; + +export const stdOutgoingListMessageSchema = z.object({ + options: contentOptionsSchema, + elements: z.array(contentElementSchema), + pagination: z.object({ + total: z.number(), + skip: z.number(), + limit: z.number(), + }), +}); + +export type StdOutgoingListMessage = z.infer< + typeof stdOutgoingListMessageSchema +>; + +export const stdOutgoingAttachmentMessageSchema = z.object({ + attachment: attachmentPayloadSchema, + quickReplies: z.array(stdQuickReplySchema).optional(), +}); + +export type StdOutgoingAttachmentMessage = z.infer< + typeof stdOutgoingAttachmentMessageSchema +>; + +export const pluginNameSchema = z + .string() + .regex(/-plugin$/) as z.ZodType; + +export const stdPluginMessageSchema = z.object({ + plugin: pluginNameSchema, + args: z.record(z.any()), +}); + +export type StdPluginMessage = z.infer; + +export const blockMessageSchema = z.union([ + z.array(z.string()), + stdOutgoingTextMessageSchema, + stdOutgoingQuickRepliesMessageSchema, + stdOutgoingButtonsMessageSchema, + stdOutgoingListMessageSchema, + stdOutgoingAttachmentMessageSchema, + stdPluginMessageSchema, +]); + +export type BlockMessage = z.infer; + +export const StdOutgoingMessageSchema = z.union([ + stdOutgoingTextMessageSchema, + stdOutgoingQuickRepliesMessageSchema, + stdOutgoingButtonsMessageSchema, + stdOutgoingListMessageSchema, + stdOutgoingAttachmentMessageSchema, +]); + +export type StdOutgoingMessage = z.infer; + +export const stdIncomingTextMessageSchema = z.object({ + text: z.string(), +}); + +export type StdIncomingTextMessage = z.infer< + typeof stdIncomingTextMessageSchema +>; + +export const stdIncomingPostBackMessageSchema = + stdIncomingTextMessageSchema.extend({ + postback: z.string(), + }); + +export type StdIncomingPostBackMessage = z.infer< + typeof stdIncomingPostBackMessageSchema +>; + +export const stdIncomingLocationMessageSchema = z.object({ + type: z.literal(PayloadType.location), + coordinates: z.object({ + lat: z.number(), + lon: z.number(), + }), +}); + +export type StdIncomingLocationMessage = z.infer< + typeof stdIncomingLocationMessageSchema +>; + +export const stdIncomingAttachmentMessageSchema = z.object({ + type: z.literal(PayloadType.attachments), + serialized_text: z.string(), + attachment: z.union([ + attachmentPayloadSchema, + z.array(attachmentPayloadSchema), + ]), +}); + +export type StdIncomingAttachmentMessage = z.infer< + typeof stdIncomingAttachmentMessageSchema +>; + +export const stdIncomingMessageSchema = z.union([ + stdIncomingTextMessageSchema, + stdIncomingPostBackMessageSchema, + stdIncomingLocationMessageSchema, + stdIncomingAttachmentMessageSchema, +]); + +export type StdIncomingMessage = z.infer; export interface IncomingMessage extends Omit { message: StdIncomingMessage; @@ -162,34 +252,149 @@ export interface OutgoingMessage extends Omit { export type AnyMessage = IncomingMessage | OutgoingMessage; -export interface StdOutgoingTextEnvelope { - format: OutgoingMessageFormat.text; - message: StdOutgoingTextMessage; -} +export const stdOutgoingTextEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.text), + message: stdOutgoingTextMessageSchema, +}); -export interface StdOutgoingQuickRepliesEnvelope { - format: OutgoingMessageFormat.quickReplies; - message: StdOutgoingQuickRepliesMessage; -} +export type StdOutgoingTextEnvelope = z.infer< + typeof stdOutgoingTextEnvelopeSchema +>; -export interface StdOutgoingButtonsEnvelope { - format: OutgoingMessageFormat.buttons; - message: StdOutgoingButtonsMessage; -} +export const stdOutgoingQuickRepliesEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.quickReplies), + message: stdOutgoingQuickRepliesMessageSchema, +}); -export interface StdOutgoingListEnvelope { - format: OutgoingMessageFormat.list | OutgoingMessageFormat.carousel; - message: StdOutgoingListMessage; -} +export type StdOutgoingQuickRepliesEnvelope = z.infer< + typeof stdOutgoingQuickRepliesEnvelopeSchema +>; -export interface StdOutgoingAttachmentEnvelope { - format: OutgoingMessageFormat.attachment; - message: StdOutgoingAttachmentMessage; -} +export const stdOutgoingButtonsEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.buttons), + message: stdOutgoingButtonsMessageSchema, +}); + +export type StdOutgoingButtonsEnvelope = z.infer< + typeof stdOutgoingButtonsEnvelopeSchema +>; + +export const stdOutgoingListEnvelopeSchema = z.object({ + format: z.enum(['list', 'carousel']), + message: stdOutgoingListMessageSchema, +}); + +export type StdOutgoingListEnvelope = z.infer< + typeof stdOutgoingListEnvelopeSchema +>; + +export const stdOutgoingAttachmentEnvelopeSchema = z.object({ + format: z.literal(OutgoingMessageFormat.attachment), + message: stdOutgoingAttachmentMessageSchema, +}); + +export type StdOutgoingAttachmentEnvelope = z.infer< + typeof stdOutgoingAttachmentEnvelopeSchema +>; -export type StdOutgoingEnvelope = - | StdOutgoingTextEnvelope - | StdOutgoingQuickRepliesEnvelope - | StdOutgoingButtonsEnvelope - | StdOutgoingListEnvelope - | StdOutgoingAttachmentEnvelope; +export const stdOutgoingEnvelopeSchema = z.union([ + stdOutgoingTextEnvelopeSchema, + stdOutgoingQuickRepliesEnvelopeSchema, + stdOutgoingButtonsEnvelopeSchema, + stdOutgoingListEnvelopeSchema, + stdOutgoingAttachmentEnvelopeSchema, +]); + +export type StdOutgoingEnvelope = z.infer; + +// is-valid-message-text validation +export const validMessageTextSchema = z.object({ + message: z.string(), +}); + +// is-message validation +const MESSAGE_REGEX = /^function \(context\) \{[^]+\}/; + +export const messageRegexSchema = z.string().regex(MESSAGE_REGEX); + +export const textSchema = z.array(z.string().max(1000)); + +const quickReplySchema = z + .object({ + content_type: z.nativeEnum(QuickReplyType), + title: z.string().max(20).optional(), + payload: z.string().max(1000).optional(), + }) + .superRefine((data, ctx) => { + // When content_type is 'text', title and payload are required. + if (data.content_type === QuickReplyType.text) { + if (data.title == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Title is required when content_type is 'text'", + path: ['title'], + }); + } + if (data.payload == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Payload is required when content_type is 'text'", + path: ['payload'], + }); + } + } + }); + +// pluginBlockMessageSchema in case of Plugin Block +export const pluginBlockMessageSchema = z + .record(z.any()) + .superRefine((data, ctx) => { + if (!('plugin' in data)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "The object must contain the 'plugin' attribute", + path: ['plugin'], + }); + } + }); + +// textBlockMessageSchema in case of Text Block +const textBlockMessageSchema = z.string().max(1000); + +const buttonMessageSchema = z.object({ + text: z.string(), + buttons: z.array(buttonSchema).max(3), +}); + +// quickReplyMessageSchema in case of QuickReply Block +const quickReplyMessageSchema = z.object({ + text: z.string(), + quickReplies: z.array(quickReplySchema).max(11).optional(), +}); + +// listBlockMessageSchema in case of List Block +const listBlockMessageSchema = z.object({ + elements: z.boolean(), +}); + +// attachmentBlockMessageSchema in case of Attachment Block +const attachmentBlockMessageSchema = z.object({ + text: z.string().max(1000).optional(), + attachment: z.object({ + type: z.nativeEnum(FileType), + payload: z.union([ + z.object({ url: z.string().url() }), + z.object({ id: z.string().nullable() }), + ]), + }), +}); + +// BlockMessage Schema +export const blockMessageObjectSchema = z.union([ + pluginBlockMessageSchema, + textBlockMessageSchema, + buttonMessageSchema, + quickReplyMessageSchema, + listBlockMessageSchema, + attachmentBlockMessageSchema, +]); diff --git a/api/src/chat/schemas/types/options.ts b/api/src/chat/schemas/types/options.ts index db1fe8b4a..f413a56de 100644 --- a/api/src/chat/schemas/types/options.ts +++ b/api/src/chat/schemas/types/options.ts @@ -1,42 +1,46 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -import { Button } from './button'; -import { OutgoingMessageFormat } from './message'; +import { z } from 'zod'; -export interface ContentOptions { - display: OutgoingMessageFormat.list | OutgoingMessageFormat.carousel; - fields: { - title: string; - subtitle: string | null; - image_url: string | null; - url?: string; - action_title?: string; - action_payload?: string; - }; - buttons: Button[]; - limit: number; - query?: any; // Waterline model criteria - entity?: string | number; // ContentTypeID - top_element_style?: 'large' | 'compact'; -} +import { buttonSchema } from './button'; -export interface BlockOptions { - typing?: number; - // In case of carousel/list message - content?: ContentOptions; - // Only if the block has next blocks - fallback?: { - active: boolean; - message: string[]; - max_attempts: number; - }; - assignTo?: string; - // plugins effects - effects?: string[]; -} +export const contentOptionsSchema = z.object({ + display: z.enum(['list', 'carousel']), + fields: z.object({ + title: z.string(), + subtitle: z.string().nullable(), + image_url: z.string().nullable(), + url: z.string().optional(), + action_title: z.string().optional(), + action_payload: z.string().optional(), + }), + buttons: z.array(buttonSchema), + limit: z.number().finite(), + query: z.any().optional(), + entity: z.union([z.string(), z.number().finite()]).optional(), + top_element_style: z.enum(['large', 'compact']).optional(), +}); + +export type ContentOptions = z.infer; + +export const BlockOptionsSchema = z.object({ + typing: z.number().optional(), + content: contentOptionsSchema.optional(), + fallback: z + .object({ + active: z.boolean(), + message: z.array(z.string()), + max_attempts: z.number().finite(), + }) + .optional(), + assignTo: z.string().optional(), + effects: z.array(z.string()).optional(), +}); + +export type BlockOptions = z.infer; diff --git a/api/src/chat/schemas/types/quick-reply.ts b/api/src/chat/schemas/types/quick-reply.ts index e58cea157..95862874b 100644 --- a/api/src/chat/schemas/types/quick-reply.ts +++ b/api/src/chat/schemas/types/quick-reply.ts @@ -9,7 +9,6 @@ import { z } from 'zod'; import { attachmentPayloadSchema } from './attachment'; -import { PayloadType } from './message'; export enum QuickReplyType { text = 'text', @@ -25,11 +24,11 @@ export const cordinatesSchema = z.object({ export const payloadSchema = z.discriminatedUnion('type', [ z.object({ - type: z.literal(PayloadType.location), + type: z.literal('location'), coordinates: cordinatesSchema, }), z.object({ - type: z.literal(PayloadType.attachments), + type: z.literal('attachments'), attachment: attachmentPayloadSchema, }), ]); diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index c7fedabf5..bab883878 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -17,7 +17,7 @@ import { I18nService } from '@/i18n/services/i18n.service'; import { LanguageService } from '@/i18n/services/language.service'; import { LoggerService } from '@/logger/logger.service'; import { PluginService } from '@/plugins/plugins.service'; -import { PluginName, PluginType } from '@/plugins/types'; +import { PluginType } from '@/plugins/types'; import { SettingService } from '@/setting/services/setting.service'; import { BaseService } from '@/utils/generics/base-service'; import { getRandom } from '@/utils/helpers/safeRandom'; @@ -568,7 +568,6 @@ export class BlockService extends BaseService< contentBlockOptions, skip, ); - const envelope: StdOutgoingEnvelope = { format: contentBlockOptions.display, message: { @@ -576,7 +575,6 @@ export class BlockService extends BaseService< options: contentBlockOptions, }, }; - return envelope; } catch (err) { this.logger.error( @@ -588,7 +586,7 @@ export class BlockService extends BaseService< } else if (blockMessage && 'plugin' in blockMessage) { const plugin = this.pluginService.findPlugin( PluginType.block, - blockMessage.plugin as PluginName, + blockMessage.plugin, ); // Process custom plugin block try { diff --git a/api/src/chat/validation-rules/is-message.ts b/api/src/chat/validation-rules/is-message.ts index 67688d941..20fc2f2b4 100644 --- a/api/src/chat/validation-rules/is-message.ts +++ b/api/src/chat/validation-rules/is-message.ts @@ -12,103 +12,40 @@ import { ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; -import Joi from 'joi'; -import { BlockMessage } from '../schemas/types/message'; +import { + BlockMessage, + blockMessageObjectSchema, + messageRegexSchema, + textSchema, +} from '../schemas/types/message'; +/* eslint-disable no-console */ export function isValidMessage(msg: any) { if (typeof msg === 'string' && msg !== '') { - // Custom code - const MESSAGE_REGEX = /^function \(context\) \{[^]+\}/; - if (!MESSAGE_REGEX.test(msg)) { - // eslint-disable-next-line - console.error('Block Model : Invalid custom code.', msg); + const result = messageRegexSchema.safeParse(msg); + if (!result.success) { + console.error('Block Model: Invalid custom code.', result.error); return false; - } else { - return true; } + return true; } else if (Array.isArray(msg)) { - // Simple text message - const textSchema = Joi.array().items(Joi.string().max(1000).required()); - const textCheck = textSchema.validate(msg); - return !textCheck.error; - } else if (typeof msg === 'object') { - if ('plugin' in msg) { - return true; - } else { - const buttonsSchema = Joi.array().items( - Joi.object().keys({ - type: Joi.string().valid('postback', 'web_url').required(), - title: Joi.string().max(20), - payload: Joi.alternatives().conditional('type', { - is: 'postback', - then: Joi.string().max(1000).required(), - otherwise: Joi.forbidden(), - }), - url: Joi.alternatives().conditional('type', { - is: 'web_url', - then: Joi.string().uri(), - otherwise: Joi.forbidden(), - }), - messenger_extensions: Joi.alternatives().conditional('type', { - is: 'web_url', - then: Joi.boolean(), - otherwise: Joi.forbidden(), - }), - webview_height_ratio: Joi.alternatives().conditional('type', { - is: 'web_url', - then: Joi.string().valid('compact', 'tall', 'full'), - otherwise: Joi.forbidden(), - }), - }), - ); - // Attachment message - const objectSchema = Joi.object().keys({ - text: Joi.string().max(1000), - attachment: Joi.object().keys({ - type: Joi.string() - .valid('image', 'audio', 'video', 'file', 'unknown') - .required(), - payload: Joi.object().keys({ - url: Joi.string().uri(), - id: Joi.string().allow(null), - }), - }), - elements: Joi.boolean(), - cards: Joi.object().keys({ - default_action: buttonsSchema.max(1), - buttons: buttonsSchema.max(3), - }), - buttons: buttonsSchema.max(3), - quickReplies: Joi.array() - .items( - Joi.object().keys({ - content_type: Joi.string() - .valid('text', 'location', 'user_phone_number', 'user_email') - .required(), - title: Joi.alternatives().conditional('content_type', { - is: 'text', - then: Joi.string().max(20).required(), - }), - payload: Joi.alternatives().conditional('content_type', { - is: 'text', - then: Joi.string().max(1000).required(), - }), - }), - ) - .max(11), - }); - const objectCheck = objectSchema.validate(msg); - if (objectCheck.error) { - // eslint-disable-next-line - console.log('Message validation failed! ', objectCheck); - } - return !objectCheck.error; + const result = textSchema.safeParse(msg); + if (!result.success) { + console.error('Block Model: Invalid text message array.', result.error); + } + return result.success; + } else if (typeof msg === 'object' && msg !== null) { + const result = blockMessageObjectSchema.safeParse(msg); + if (!result.success) { + console.error('Block Model: Object validation failed!', result.error); } - } else { - return false; + return result.success; } + console.log('Validation reached default false'); + return false; } +/* eslint-enable no-console */ @ValidatorConstraint({ async: false }) export class MessageValidator implements ValidatorConstraintInterface { diff --git a/api/src/chat/validation-rules/is-valid-message-text.ts b/api/src/chat/validation-rules/is-valid-message-text.ts index 485e78bce..caf63f510 100644 --- a/api/src/chat/validation-rules/is-valid-message-text.ts +++ b/api/src/chat/validation-rules/is-valid-message-text.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -11,6 +11,7 @@ import { registerDecorator, ValidationOptions } from 'class-validator'; import { StdIncomingMessage, StdOutgoingTextMessage, + validMessageTextSchema, } from '../schemas/types/message'; export function IsValidMessageText(validationOptions?: ValidationOptions) { @@ -21,7 +22,7 @@ export function IsValidMessageText(validationOptions?: ValidationOptions) { options: validationOptions, validator: { validate(message: StdOutgoingTextMessage | StdIncomingMessage) { - return !!(message as StdOutgoingTextMessage).text; + return validMessageTextSchema.safeParse(message).success; }, }, });