@gamastudio/sendwave-provider 0.0.6 → 0.0.7-dev1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,5 +7,7 @@ export interface ParsedMessage {
7
7
  type: string;
8
8
  media?: string;
9
9
  caption?: string;
10
- originalPayload: any;
10
+ originalPayload: {
11
+ key: any;
12
+ };
11
13
  }
@@ -10,6 +10,7 @@ export interface GlobalVendorArgs {
10
10
  url?: string;
11
11
  apiKey: string;
12
12
  port?: number;
13
+ host?: string;
13
14
  delay?: number;
14
15
  linkPreview?: boolean;
15
16
  message?: {
@@ -25,6 +26,17 @@ export interface GlobalVendorArgs {
25
26
  endTimeout?: number;
26
27
  warningMessage?: string;
27
28
  };
29
+ payloadLimits?: {
30
+ json?: string;
31
+ urlencoded?: string;
32
+ text?: string;
33
+ media?: {
34
+ image?: string;
35
+ video?: string;
36
+ audio?: string;
37
+ document?: string;
38
+ };
39
+ };
28
40
  }
29
41
  export interface Message {
30
42
  type: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamastudio/sendwave-provider",
3
- "version": "0.0.6",
3
+ "version": "0.0.7-dev1",
4
4
  "description": "Librería para interactuar con Sendwave usando configuración dinámica.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "author": "Ameth Galarcio <amethgm@gmail.com>",
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
- "@builderbot/bot": "^1.3.1",
25
+ "@builderbot/bot": "^1.2.9",
26
26
  "@gamastudio/colorslog": "^0.1.6",
27
27
  "@types/mime-types": "^2.1.4",
28
28
  "axios": "^1.11.0",
@@ -54,8 +54,35 @@ class SendWaveCore extends node_events_1.EventEmitter {
54
54
  }
55
55
  catch (e) {
56
56
  console.error("[IncomingMsg Error]:", e);
57
+ // Handle PayloadTooLargeError specifically
58
+ if (e.name === 'PayloadTooLargeError' || e.type === 'entity.too.large') {
59
+ console.error(`[PayloadTooLarge] Request from ${req.ip || 'unknown'} exceeded size limit`);
60
+ res.statusCode = 413;
61
+ res.setHeader('Content-Type', 'application/json');
62
+ return res.end(JSON.stringify({
63
+ error: 'Payload too large',
64
+ message: 'El archivo o mensaje es demasiado grande',
65
+ code: 'PAYLOAD_TOO_LARGE'
66
+ }));
67
+ }
68
+ // Handle timeout errors
69
+ if (e.type === 'request.timeout') {
70
+ console.error(`[RequestTimeout] Request from ${req.ip || 'unknown'} timed out`);
71
+ res.statusCode = 408;
72
+ res.setHeader('Content-Type', 'application/json');
73
+ return res.end(JSON.stringify({
74
+ error: 'Request timeout',
75
+ message: 'La solicitud ha tardado demasiado',
76
+ code: 'REQUEST_TIMEOUT'
77
+ }));
78
+ }
57
79
  res.statusCode = 500;
58
- return res.end("Internal Server Error");
80
+ res.setHeader('Content-Type', 'application/json');
81
+ return res.end(JSON.stringify({
82
+ error: 'Internal Server Error',
83
+ message: 'Error interno del servidor',
84
+ code: 'INTERNAL_ERROR'
85
+ }));
59
86
  }
60
87
  };
61
88
  this.messageBuffers = {};
@@ -14,6 +14,7 @@ export declare class SendWaveProvider extends ProviderClass<SendWaveCore> {
14
14
  sender: SenderMessage;
15
15
  globalVendorArgs: GlobalVendorArgs;
16
16
  constructor(args: Partial<GlobalVendorArgs>);
17
+ private setupGlobalErrorHandlers;
17
18
  protected initVendor(): Promise<any>;
18
19
  protected beforeHttpServerInit: () => Promise<void>;
19
20
  protected getApiQrConnect: () => Promise<void>;
@@ -22,6 +23,7 @@ export declare class SendWaveProvider extends ProviderClass<SendWaveCore> {
22
23
  path: string;
23
24
  }): Promise<string>;
24
25
  createInstance(): void;
26
+ private parseSize;
25
27
  busEvents: () => ({
26
28
  event: string;
27
29
  func: (payload: BotContext) => void;
@@ -48,6 +50,7 @@ export declare class SendWaveProvider extends ProviderClass<SendWaveCore> {
48
50
  sendButton(...args: Parameters<SenderMessage["sendButton"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
49
51
  sendReaction(...args: Parameters<SenderMessage["sendReaction"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
50
52
  sendLocation(...args: Parameters<SenderMessage["sendLocation"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
53
+ sendMedia(...args: Parameters<SenderMessage["sendMedia"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
51
54
  /**
52
55
  * Reset user timeout for queue flow system
53
56
  */
@@ -27,6 +27,7 @@ class SendWaveProvider extends bot_1.ProviderClass {
27
27
  name: "bot",
28
28
  url: "https://sendwave-api.gamastudio.co",
29
29
  port: 3000,
30
+ host: "0.0.0.0", // Listen on all interfaces by default
30
31
  apiKey: "",
31
32
  delay: 0,
32
33
  linkPreview: true,
@@ -40,10 +41,38 @@ class SendWaveProvider extends bot_1.ProviderClass {
40
41
  endTimeout: 2 * 60 * 1000, // 2 minutes
41
42
  warningMessage: "⏳ Parece que has estado inactivo. ¿Sigues ahí?",
42
43
  },
44
+ payloadLimits: {
45
+ json: '50mb',
46
+ urlencoded: '50mb',
47
+ text: '10mb',
48
+ media: {
49
+ image: '10mb',
50
+ video: '100mb',
51
+ audio: '50mb',
52
+ document: '25mb',
53
+ },
54
+ },
43
55
  };
44
56
  this.beforeHttpServerInit = async () => {
57
+ // Configure body parser with large payload limits
58
+ const payloadLimits = this.globalVendorArgs.payloadLimits || {};
45
59
  this.server = this.server
46
- .use(body_parser_1.default.json())
60
+ .use(body_parser_1.default.json({
61
+ limit: payloadLimits.json || '50mb',
62
+ type: 'application/json'
63
+ }))
64
+ .use(body_parser_1.default.urlencoded({
65
+ limit: payloadLimits.urlencoded || '50mb',
66
+ extended: true
67
+ }))
68
+ .use(body_parser_1.default.text({
69
+ limit: payloadLimits.text || '10mb',
70
+ type: 'text/*'
71
+ }))
72
+ .use(body_parser_1.default.raw({
73
+ limit: payloadLimits.json || '50mb',
74
+ type: 'application/octet-stream'
75
+ }))
47
76
  .use((req, _, next) => {
48
77
  req["globalVendorArgs"] = this.globalVendorArgs;
49
78
  return next();
@@ -117,6 +146,11 @@ class SendWaveProvider extends bot_1.ProviderClass {
117
146
  ];
118
147
  this.initAll = (port, opts) => {
119
148
  this.globalVendorArgs.port = port;
149
+ // Override the server binding to listen on all interfaces
150
+ if (this.globalVendorArgs.host === "0.0.0.0") {
151
+ process.env.HOST = "0.0.0.0";
152
+ process.env.HOSTNAME = "0.0.0.0";
153
+ }
120
154
  const methods = {
121
155
  sendMessage: this.sendMessage,
122
156
  sendList: this.sendList,
@@ -150,6 +184,12 @@ class SendWaveProvider extends bot_1.ProviderClass {
150
184
  });
151
185
  this.afterHttpServerInit();
152
186
  });
187
+ })
188
+ .catch((error) => {
189
+ console.error("[Provider Error]:", error);
190
+ if (error.type === 'entity.too.large') {
191
+ console.error("[Provider] PayloadTooLarge error caught at provider level");
192
+ }
153
193
  });
154
194
  return;
155
195
  };
@@ -174,6 +214,27 @@ class SendWaveProvider extends bot_1.ProviderClass {
174
214
  // Puedes emitir eventos a websockets aquí, notificar a frontends, etc.
175
215
  });
176
216
  this.sender = new sender_1.SenderMessage(this.globalVendorArgs);
217
+ // Add global error handlers for payload errors
218
+ this.setupGlobalErrorHandlers();
219
+ }
220
+ setupGlobalErrorHandlers() {
221
+ // Handle uncaught PayloadTooLarge errors
222
+ process.on('uncaughtException', (error) => {
223
+ if (error.name === 'PayloadTooLargeError' || error.type === 'entity.too.large') {
224
+ console.error('[Global] PayloadTooLarge error intercepted:', error.message);
225
+ // Don't exit process for this type of error
226
+ return;
227
+ }
228
+ // For other uncaught exceptions, log and continue
229
+ console.error('[Global] Uncaught exception:', error);
230
+ });
231
+ process.on('unhandledRejection', (reason) => {
232
+ if (reason && (reason.name === 'PayloadTooLargeError' || reason.type === 'entity.too.large')) {
233
+ console.error('[Global] PayloadTooLarge rejection intercepted:', reason.message);
234
+ return;
235
+ }
236
+ console.error('[Global] Unhandled rejection:', reason);
237
+ });
177
238
  }
178
239
  initVendor() {
179
240
  const vendor = new core_1.SendWaveCore(this.globalVendorArgs.port, this.queue, this.globalVendorArgs);
@@ -223,6 +284,8 @@ class SendWaveProvider extends bot_1.ProviderClass {
223
284
  this.globalVendorArgs.url.trim()
224
285
  ? this.globalVendorArgs.url.trim()
225
286
  : "https://sendwave-api.gamastudio.co";
287
+ const payloadLimits = this.globalVendorArgs.payloadLimits || {};
288
+ const maxContentLength = this.parseSize(payloadLimits.json || '50mb');
226
289
  this.sendwaveApi = axios_1.default.create({
227
290
  baseURL: url,
228
291
  httpsAgent: new https_1.Agent({ rejectUnauthorized: false }),
@@ -230,8 +293,46 @@ class SendWaveProvider extends bot_1.ProviderClass {
230
293
  "Content-Type": "application/json",
231
294
  apikey: this.globalVendorArgs.apiKey,
232
295
  },
296
+ // Large file support configuration
297
+ maxContentLength: maxContentLength,
298
+ maxBodyLength: maxContentLength,
299
+ timeout: 300000, // 5 minutes timeout for large files
300
+ });
301
+ // Add request interceptor for large file logging
302
+ this.sendwaveApi.interceptors.request.use((config) => {
303
+ const contentLength = config.headers['content-length'] ||
304
+ (config.data ? JSON.stringify(config.data).length : 0);
305
+ if (contentLength > 1024 * 1024) { // Log requests larger than 1MB
306
+ console.log(`[LargeRequest] Sending ${Math.round(contentLength / 1024 / 1024 * 100) / 100}MB to ${config.url}`);
307
+ }
308
+ return config;
309
+ }, (error) => Promise.reject(error));
310
+ // Add response interceptor for error handling
311
+ this.sendwaveApi.interceptors.response.use((response) => response, (error) => {
312
+ var _a;
313
+ if (error.code === 'ECONNABORTED') {
314
+ console.error('[RequestTimeout] Large file upload timed out');
315
+ }
316
+ else if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 413) {
317
+ console.error('[PayloadTooLarge] Server rejected large payload');
318
+ }
319
+ return Promise.reject(error);
233
320
  });
234
321
  }
322
+ parseSize(size) {
323
+ const units = {
324
+ 'b': 1,
325
+ 'kb': 1024,
326
+ 'mb': 1024 * 1024,
327
+ 'gb': 1024 * 1024 * 1024
328
+ };
329
+ const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
330
+ if (!match)
331
+ return 50 * 1024 * 1024; // Default 50MB
332
+ const value = parseFloat(match[1]);
333
+ const unit = match[2] || 'b';
334
+ return Math.floor(value * units[unit]);
335
+ }
235
336
  async listenOnEvents(vendor) {
236
337
  const listEvents = this.busEvents();
237
338
  for (const { event, func } of listEvents) {
@@ -246,38 +347,60 @@ class SendWaveProvider extends bot_1.ProviderClass {
246
347
  });
247
348
  }
248
349
  async sendMessage(from, text, options) {
249
- options = { ...options["options"] };
250
- let mss = { from, text, options };
251
- if (options.media) {
252
- const { media, mediaType, fileName } = await detectorMedia_1.detectorMedia.processMedia(options.media);
253
- switch (mediaType) {
254
- case "image":
255
- return await this.sender.sendImage({
256
- ...mss,
257
- url: media,
258
- text: text,
259
- });
260
- case "video":
261
- return await this.sender.sendVideo({
262
- ...mss,
263
- url: media,
264
- text: text,
265
- });
266
- case "audio":
267
- return await this.sender.sendVoice({ ...mss, url: media });
268
- case "document":
269
- return await this.sender.sendFile({
270
- ...mss,
271
- url: media,
272
- fileName,
273
- text: text,
274
- });
275
- default:
276
- return await this.sender.sendText(mss);
350
+ var _a, _b;
351
+ try {
352
+ options = { ...options["options"] };
353
+ let mss = { from, text, options };
354
+ if (options.media) {
355
+ // Initialize detector with payload limits from global args
356
+ if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
357
+ detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
358
+ }
359
+ const { media, mediaType, fileName, size } = await detectorMedia_1.detectorMedia.processMedia(options.media);
360
+ switch (mediaType) {
361
+ case "image":
362
+ return await this.sender.sendImage({
363
+ ...mss,
364
+ url: media,
365
+ text: text,
366
+ });
367
+ case "video":
368
+ return await this.sender.sendVideo({
369
+ ...mss,
370
+ url: media,
371
+ text: text,
372
+ });
373
+ case "audio":
374
+ return await this.sender.sendVoice({ ...mss, url: media });
375
+ case "document":
376
+ return await this.sender.sendFile({
377
+ ...mss,
378
+ url: media,
379
+ fileName,
380
+ text: text,
381
+ });
382
+ default:
383
+ return await this.sender.sendText(mss);
384
+ }
385
+ }
386
+ else {
387
+ return await this.sender.sendText(mss);
277
388
  }
278
389
  }
279
- else {
280
- return await this.sender.sendText(mss);
390
+ catch (error) {
391
+ // Enhanced error handling for large files
392
+ if (error.message && error.message.includes('exceeds limit')) {
393
+ console.error(`[SendMessage Error] File size validation failed: ${error.message}`);
394
+ throw new Error(`File too large: ${error.message}`);
395
+ }
396
+ else if (error.message && error.message.includes('too large')) {
397
+ console.error(`[SendMessage Error] Large file error: ${error.message}`);
398
+ throw error;
399
+ }
400
+ else {
401
+ console.error(`[SendMessage Error] ${error.message || 'Unknown error'}`);
402
+ throw error;
403
+ }
281
404
  }
282
405
  }
283
406
  // Métodos de conveniencia para mantener la API limpia
@@ -314,6 +437,9 @@ class SendWaveProvider extends bot_1.ProviderClass {
314
437
  sendLocation(...args) {
315
438
  return this.sender.sendLocation(...args);
316
439
  }
440
+ sendMedia(...args) {
441
+ return this.sender.sendMedia(...args);
442
+ }
317
443
  // Queue Flow Management Methods
318
444
  /**
319
445
  * Reset user timeout for queue flow system
@@ -3,10 +3,12 @@ export declare class SenderMessage implements ProviderInterface {
3
3
  private sendwaveApi?;
4
4
  private globalVendorArgs?;
5
5
  constructor(args: Partial<GlobalVendorArgs>);
6
+ private parseSize;
6
7
  sendPresence(data: SendPresence): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
7
8
  sendText(data: SendMessage): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
8
9
  sendList(data: SendList): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
9
10
  sendMedia(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
11
+ private getDefaultMimeType;
10
12
  sendFile(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
11
13
  sendImage(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
12
14
  sendAudio(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
@@ -9,6 +9,8 @@ const axios_1 = __importDefault(require("axios"));
9
9
  const detectorMedia_1 = require("../utils/detectorMedia");
10
10
  class SenderMessage {
11
11
  constructor(args) {
12
+ const payloadLimits = args.payloadLimits || {};
13
+ const maxContentLength = this.parseSize(payloadLimits.json || "50mb");
12
14
  this.sendwaveApi = axios_1.default.create({
13
15
  baseURL: args.url,
14
16
  httpsAgent: new https_1.Agent({ rejectUnauthorized: false }),
@@ -16,9 +18,26 @@ class SenderMessage {
16
18
  "Content-Type": "application/json",
17
19
  apikey: args.apiKey,
18
20
  },
21
+ maxContentLength: maxContentLength,
22
+ maxBodyLength: maxContentLength,
23
+ timeout: 300000, // 5 minutes for large files
19
24
  });
20
25
  this.globalVendorArgs = args;
21
26
  }
27
+ parseSize(size) {
28
+ const units = {
29
+ b: 1,
30
+ kb: 1024,
31
+ mb: 1024 * 1024,
32
+ gb: 1024 * 1024 * 1024,
33
+ };
34
+ const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
35
+ if (!match)
36
+ return 50 * 1024 * 1024; // Default 50MB
37
+ const value = parseFloat(match[1]);
38
+ const unit = match[2] || "b";
39
+ return Math.floor(value * units[unit]);
40
+ }
22
41
  async sendPresence(data) {
23
42
  var _a, _b;
24
43
  try {
@@ -96,10 +115,50 @@ class SenderMessage {
96
115
  }
97
116
  }
98
117
  async sendMedia(data) {
99
- var _a, _b;
118
+ var _a, _b, _c, _d, _e, _f, _g, _h;
100
119
  try {
101
- const { media, mimeType, mediaType } = await detectorMedia_1.detectorMedia.processMedia(data.url);
102
- return await ((_a = this.sendwaveApi) === null || _a === void 0 ? void 0 : _a.post(`/message/sendMedia/${(_b = this.globalVendorArgs) === null || _b === void 0 ? void 0 : _b.name}`, {
120
+ // Initialize detector with payload limits from global args
121
+ if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
122
+ detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
123
+ }
124
+ let media;
125
+ let mimeType;
126
+ let mediaType;
127
+ // If mediaType is already specified (from sendImage, sendVideo, etc), use it
128
+ if (data.mediaType) {
129
+ console.log(`[SendMedia] Using predefined mediaType: ${data.mediaType}`);
130
+ // Just validate size and get media content without detection
131
+ if (detectorMedia_1.detectorMedia.isRemoteUrl(data.url)) {
132
+ // For remote URLs, validate size and use URL directly
133
+ await detectorMedia_1.detectorMedia.validateUrlSize(data.url, data.mediaType);
134
+ media = data.url;
135
+ // Get actual MIME type from server if possible
136
+ try {
137
+ const { mimeType: detectedMime } = await detectorMedia_1.detectorMedia.getMimeTypeFromUrl(data.url);
138
+ mimeType = detectedMime || this.getDefaultMimeType(data.mediaType);
139
+ }
140
+ catch {
141
+ mimeType = this.getDefaultMimeType(data.mediaType);
142
+ }
143
+ }
144
+ else {
145
+ // For local files, convert to base64
146
+ const result = await detectorMedia_1.detectorMedia.convertPathToBase64(data.url);
147
+ media = result.media;
148
+ mimeType = result.mimeType;
149
+ }
150
+ mediaType = data.mediaType;
151
+ }
152
+ else {
153
+ // If no mediaType specified, detect it (for generic sendMedia calls)
154
+ console.log(`[SendMedia] Auto-detecting mediaType for: ${data.url}`);
155
+ const result = await detectorMedia_1.detectorMedia.processMedia(data.url);
156
+ media = result.media;
157
+ mimeType = result.mimeType;
158
+ mediaType = result.mediaType;
159
+ }
160
+ console.log(`[SendMedia] Final - MediaType: ${mediaType}, MimeType: ${mimeType}`);
161
+ return await ((_c = this.sendwaveApi) === null || _c === void 0 ? void 0 : _c.post(`/message/sendMedia/${(_d = this.globalVendorArgs) === null || _d === void 0 ? void 0 : _d.name}`, {
103
162
  number: data.from.split("@")[0],
104
163
  caption: data.text,
105
164
  mediatype: mediaType,
@@ -110,9 +169,35 @@ class SenderMessage {
110
169
  }));
111
170
  }
112
171
  catch (e) {
113
- console.error(e.response.data.response.message);
172
+ // Enhanced error handling for large files
173
+ if (e.message && e.message.includes("exceeds limit")) {
174
+ console.error(`[SendMedia Error] File size validation failed: ${e.message}`);
175
+ throw new Error(`File too large: ${e.message}`);
176
+ }
177
+ else if (((_e = e.response) === null || _e === void 0 ? void 0 : _e.status) === 413) {
178
+ console.error("[SendMedia Error] Server rejected large payload");
179
+ throw new Error("File too large for server");
180
+ }
181
+ else if (e.code === "ECONNABORTED") {
182
+ console.error("[SendMedia Error] Request timeout - file may be too large");
183
+ throw new Error("Upload timeout - file may be too large");
184
+ }
185
+ else {
186
+ const errorMsg = ((_h = (_g = (_f = e.response) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.response) === null || _h === void 0 ? void 0 : _h.message) || e.message || "Unknown error";
187
+ console.error(`[SendMedia Error] ${errorMsg}`);
188
+ throw new Error(errorMsg);
189
+ }
114
190
  }
115
191
  }
192
+ getDefaultMimeType(mediaType) {
193
+ const defaults = {
194
+ image: 'image/jpeg',
195
+ video: 'video/mp4',
196
+ audio: 'audio/mpeg',
197
+ document: 'application/octet-stream'
198
+ };
199
+ return defaults[mediaType] || 'application/octet-stream';
200
+ }
116
201
  async sendFile(data) {
117
202
  try {
118
203
  return await this.sendMedia({
@@ -159,22 +244,43 @@ class SenderMessage {
159
244
  }
160
245
  }
161
246
  async sendVoice(data) {
162
- var _a, _b;
247
+ var _a, _b, _c, _d, _e, _f, _g, _h;
163
248
  try {
249
+ // Initialize detector with payload limits from global args
250
+ if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
251
+ detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
252
+ }
164
253
  const { media } = await detectorMedia_1.detectorMedia.processMedia(data.url);
165
254
  await this.sendPresence({
166
255
  from: data.from,
167
256
  presence: "recording",
168
257
  delay: data.delay || 2000,
169
258
  });
170
- return await ((_a = this.sendwaveApi) === null || _a === void 0 ? void 0 : _a.post(`/message/sendWhatsAppAudio/${(_b = this.globalVendorArgs) === null || _b === void 0 ? void 0 : _b.name}`, {
259
+ return await ((_c = this.sendwaveApi) === null || _c === void 0 ? void 0 : _c.post(`/message/sendWhatsAppAudio/${(_d = this.globalVendorArgs) === null || _d === void 0 ? void 0 : _d.name}`, {
171
260
  number: data.from,
172
261
  audio: media,
173
262
  ...this.globalVendorArgs,
174
263
  }));
175
264
  }
176
265
  catch (e) {
177
- console.error(e.response.data.response.message);
266
+ // Enhanced error handling for large audio files
267
+ if (e.message && e.message.includes("exceeds limit")) {
268
+ console.error(`[SendVoice Error] File size validation failed: ${e.message}`);
269
+ throw new Error(`Audio file too large: ${e.message}`);
270
+ }
271
+ else if (((_e = e.response) === null || _e === void 0 ? void 0 : _e.status) === 413) {
272
+ console.error("[SendVoice Error] Server rejected large payload");
273
+ throw new Error("Audio file too large for server");
274
+ }
275
+ else if (e.code === "ECONNABORTED") {
276
+ console.error("[SendVoice Error] Request timeout - audio file may be too large");
277
+ throw new Error("Upload timeout - audio file may be too large");
278
+ }
279
+ else {
280
+ const errorMsg = ((_h = (_g = (_f = e.response) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.response) === null || _h === void 0 ? void 0 : _h.message) || e.message || "Unknown error";
281
+ console.error(`[SendVoice Error] ${errorMsg}`);
282
+ throw new Error(errorMsg);
283
+ }
178
284
  }
179
285
  }
180
286
  async sendButton(data) {
@@ -3,19 +3,36 @@ interface DetectorMediaProps {
3
3
  mimeType?: string;
4
4
  mediaType?: string;
5
5
  fileName?: string;
6
+ size?: number;
7
+ }
8
+ interface MediaLimits {
9
+ image?: string;
10
+ video?: string;
11
+ audio?: string;
12
+ document?: string;
6
13
  }
7
14
  export declare class DetectorMedia {
8
15
  static instance: DetectorMedia;
9
- static getInstance(): DetectorMedia;
16
+ private mediaLimits;
17
+ constructor(limits?: MediaLimits);
18
+ static getInstance(limits?: MediaLimits): DetectorMedia;
19
+ updateLimits(limits: MediaLimits): void;
20
+ private parseSize;
21
+ private validateFileSize;
22
+ validateUrlSize(url: string, mediaType: string): Promise<number>;
10
23
  isRemoteUrl(url: string): boolean;
11
24
  convertPathToBase64(filePath: string): Promise<DetectorMediaProps>;
12
- getMimeTypeFromUrl(url: string): Promise<string | null>;
25
+ getMimeTypeFromUrl(url: string): Promise<{
26
+ mediaType: string | null;
27
+ mimeType: string | null;
28
+ }>;
13
29
  extractFileNameFromUrl(url: string): Promise<string>;
14
30
  /**
15
31
  * Método unificado que acepta path o url y devuelve:
16
32
  * - media (base64 si es path, url si es remota)
17
33
  * - mimeType
18
34
  * - fileName
35
+ * - size
19
36
  */
20
37
  processMedia(input: string): Promise<DetectorMediaProps>;
21
38
  mapMimeToCategory(mimeType: string): "image" | "video" | "audio" | "document" | "unknown";
@@ -9,12 +9,79 @@ const path_1 = __importDefault(require("path"));
9
9
  const mime_types_1 = __importDefault(require("mime-types"));
10
10
  const axios_1 = __importDefault(require("axios"));
11
11
  class DetectorMedia {
12
- static getInstance() {
12
+ constructor(limits) {
13
+ this.mediaLimits = {
14
+ image: "10mb",
15
+ video: "100mb",
16
+ audio: "50mb",
17
+ document: "25mb",
18
+ ...limits,
19
+ };
20
+ }
21
+ static getInstance(limits) {
13
22
  if (!DetectorMedia.instance) {
14
- DetectorMedia.instance = new DetectorMedia();
23
+ DetectorMedia.instance = new DetectorMedia(limits);
15
24
  }
16
25
  return DetectorMedia.instance;
17
26
  }
27
+ updateLimits(limits) {
28
+ this.mediaLimits = { ...this.mediaLimits, ...limits };
29
+ }
30
+ parseSize(size) {
31
+ const units = {
32
+ b: 1,
33
+ kb: 1024,
34
+ mb: 1024 * 1024,
35
+ gb: 1024 * 1024 * 1024,
36
+ };
37
+ const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
38
+ if (!match)
39
+ return 10 * 1024 * 1024; // Default 10MB
40
+ const value = parseFloat(match[1]);
41
+ const unit = match[2] || "b";
42
+ return Math.floor(value * units[unit]);
43
+ }
44
+ validateFileSize(filePath, mediaType) {
45
+ return new Promise((resolve, reject) => {
46
+ promises_1.default.stat(filePath)
47
+ .then((stats) => {
48
+ const fileSize = stats.size;
49
+ const limitStr = this.mediaLimits[mediaType] || "10mb";
50
+ const limit = this.parseSize(limitStr);
51
+ if (fileSize > limit) {
52
+ const fileSizeMB = Math.round((fileSize / 1024 / 1024) * 100) / 100;
53
+ const limitMB = Math.round((limit / 1024 / 1024) * 100) / 100;
54
+ reject(new Error(`File size ${fileSizeMB}MB exceeds limit of ${limitMB}MB for ${mediaType} files`));
55
+ }
56
+ else {
57
+ resolve();
58
+ }
59
+ })
60
+ .catch(reject);
61
+ });
62
+ }
63
+ async validateUrlSize(url, mediaType) {
64
+ try {
65
+ const response = await axios_1.default.head(url);
66
+ const contentLength = parseInt(response.headers["content-length"] || "0");
67
+ if (contentLength > 0) {
68
+ const limitStr = this.mediaLimits[mediaType] || "10mb";
69
+ const limit = this.parseSize(limitStr);
70
+ if (contentLength > limit) {
71
+ const fileSizeMB = Math.round((contentLength / 1024 / 1024) * 100) / 100;
72
+ const limitMB = Math.round((limit / 1024 / 1024) * 100) / 100;
73
+ throw new Error(`Remote file size ${fileSizeMB}MB exceeds limit of ${limitMB}MB for ${mediaType} files`);
74
+ }
75
+ }
76
+ return contentLength;
77
+ }
78
+ catch (error) {
79
+ if (error.message.includes("exceeds limit")) {
80
+ throw error;
81
+ }
82
+ return 0;
83
+ }
84
+ }
18
85
  isRemoteUrl(url) {
19
86
  try {
20
87
  const u = new URL(url);
@@ -25,28 +92,36 @@ class DetectorMedia {
25
92
  }
26
93
  }
27
94
  async convertPathToBase64(filePath) {
28
- const fileBuffer = await promises_1.default.readFile(filePath);
29
95
  const mediaType = this.mapMimeToCategory(mime_types_1.default.lookup(filePath).toString());
30
96
  if (!mediaType)
31
97
  throw new Error("No se pudo determinar el tipo MIME del archivo");
98
+ // Validate file size before processing
99
+ await this.validateFileSize(filePath, mediaType);
100
+ const fileBuffer = await promises_1.default.readFile(filePath);
32
101
  const base64Data = fileBuffer.toString("base64");
33
102
  const media = base64Data;
34
103
  const fileName = path_1.default.basename(filePath);
104
+ const stats = await promises_1.default.stat(filePath);
35
105
  return {
36
106
  media,
37
107
  mediaType,
38
108
  fileName,
39
109
  mimeType: mime_types_1.default.lookup(filePath).toString(),
110
+ size: stats.size,
40
111
  };
41
112
  }
42
113
  async getMimeTypeFromUrl(url) {
43
114
  try {
44
115
  const res = await axios_1.default.head(url);
45
- const mediaType = this.mapMimeToCategory(res.headers["content-type"]);
46
- return mediaType || null;
116
+ const contentType = res.headers["content-type"];
117
+ const mediaType = this.mapMimeToCategory(contentType);
118
+ return {
119
+ mediaType: mediaType || null,
120
+ mimeType: contentType || null,
121
+ };
47
122
  }
48
123
  catch {
49
- return null;
124
+ return { mediaType: null, mimeType: null };
50
125
  }
51
126
  }
52
127
  async extractFileNameFromUrl(url) {
@@ -60,32 +135,41 @@ class DetectorMedia {
60
135
  * - media (base64 si es path, url si es remota)
61
136
  * - mimeType
62
137
  * - fileName
138
+ * - size
63
139
  */
64
140
  async processMedia(input) {
65
141
  if (this.isRemoteUrl(input)) {
66
- const mediaType = await this.getMimeTypeFromUrl(input);
67
- if (!mediaType)
142
+ const { mediaType, mimeType } = await this.getMimeTypeFromUrl(input);
143
+ if (!mediaType || !mimeType)
68
144
  throw new Error("No se pudo determinar el tipo MIME de la URL remota");
145
+ // Validate remote file size
146
+ const size = await this.validateUrlSize(input, mediaType);
69
147
  const fileName = await this.extractFileNameFromUrl(input);
70
148
  return {
71
149
  media: input,
72
- mimeType: mime_types_1.default.lookup(input).toString(),
150
+ mimeType: mimeType,
73
151
  fileName,
74
152
  mediaType,
153
+ size,
75
154
  };
76
155
  }
77
156
  return this.convertPathToBase64(input);
78
157
  }
79
158
  mapMimeToCategory(mimeType) {
80
- if (mimeType.startsWith("image/"))
159
+ if (!mimeType) {
160
+ return "unknown";
161
+ }
162
+ const normalizedMimeType = mimeType.toLowerCase().split(";")[0].trim();
163
+ if (normalizedMimeType.startsWith("image/"))
81
164
  return "image";
82
- if (mimeType.startsWith("video/"))
165
+ if (normalizedMimeType.startsWith("video/"))
83
166
  return "video";
84
- if (mimeType.startsWith("audio/"))
167
+ if (normalizedMimeType.startsWith("audio/"))
85
168
  return "audio";
86
- if (mimeType.startsWith("application/") || mimeType.startsWith("text/"))
169
+ if (normalizedMimeType.startsWith("application/") ||
170
+ normalizedMimeType.startsWith("text/"))
87
171
  return "document";
88
- return "unknown";
172
+ return "document"; // Default to document instead of unknown for better compatibility
89
173
  }
90
174
  }
91
175
  exports.DetectorMedia = DetectorMedia;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamastudio/sendwave-provider",
3
- "version": "0.0.6",
3
+ "version": "0.0.7-dev1",
4
4
  "description": "Librería para interactuar con Sendwave usando configuración dinámica.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "author": "Ameth Galarcio <amethgm@gmail.com>",
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
- "@builderbot/bot": "^1.3.1",
25
+ "@builderbot/bot": "^1.2.9",
26
26
  "@gamastudio/colorslog": "^0.1.6",
27
27
  "@types/mime-types": "^2.1.4",
28
28
  "axios": "^1.11.0",