@rempays/shared-core 1.0.2-beta.11 → 1.0.2-beta.13

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.
@@ -46,12 +46,9 @@ export declare class ChatService {
46
46
  listChatsByBusiness(businessId: string, status: string, params?: PaginationParams): Promise<PaginatedResult<Chat>>;
47
47
  /**
48
48
  * Lista todos los chats de un business (todos los status)
49
+ * Nota: Hace múltiples queries, una por cada status común
49
50
  */
50
51
  listAllChatsByBusiness(businessId: string, params?: PaginationParams): Promise<PaginatedResult<Chat>>;
51
- /**
52
- * Incrementa el contador de mensajes no leídos
53
- */
54
- incrementUnreadCount(chatId: string): Promise<void>;
55
52
  /**
56
53
  * Marca todos los mensajes como leídos
57
54
  */
@@ -242,48 +242,33 @@ class ChatService {
242
242
  }
243
243
  /**
244
244
  * Lista todos los chats de un business (todos los status)
245
+ * Nota: Hace múltiples queries, una por cada status común
245
246
  */
246
247
  async listAllChatsByBusiness(businessId, params = {}) {
247
- const { limit = 20, lastEvaluatedKey } = params;
248
- const result = await this.client.send(new client_dynamodb_1.QueryCommand({
249
- TableName: this.tableName,
250
- IndexName: 'GSI1',
251
- KeyConditionExpression: 'begins_with(GSI1PK, :prefix)',
252
- ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
253
- ':prefix': `BUSINESS#${businessId}#`,
254
- }),
255
- ScanIndexForward: false,
256
- Limit: limit,
257
- ExclusiveStartKey: lastEvaluatedKey ? (0, util_dynamodb_1.marshall)(lastEvaluatedKey) : undefined,
258
- }));
259
- const items = result.Items?.map((item) => {
260
- const data = (0, util_dynamodb_1.unmarshall)(item);
261
- const { PK, SK, GSI1PK, GSI1SK, ...chat } = data;
262
- return chat;
263
- }) || [];
248
+ const { limit = 20 } = params;
249
+ const statuses = ['active', 'closed', 'pending', 'archived'];
250
+ const allChats = [];
251
+ // Query para cada status
252
+ for (const status of statuses) {
253
+ const result = await this.listChatsByBusiness(businessId, status, {
254
+ limit: Math.ceil(limit / statuses.length),
255
+ });
256
+ allChats.push(...result.items);
257
+ // Si ya tenemos suficientes, parar
258
+ if (allChats.length >= limit) {
259
+ break;
260
+ }
261
+ }
262
+ // Ordenar por lastMessageTimestamp descendente
263
+ allChats.sort((a, b) => b.lastMessageTimestamp - a.lastMessageTimestamp);
264
+ // Limitar resultados
265
+ const items = allChats.slice(0, limit);
264
266
  return {
265
267
  items,
266
- lastEvaluatedKey: result.LastEvaluatedKey ? (0, util_dynamodb_1.unmarshall)(result.LastEvaluatedKey) : undefined,
267
- hasMore: !!result.LastEvaluatedKey,
268
+ lastEvaluatedKey: undefined,
269
+ hasMore: allChats.length > limit,
268
270
  };
269
271
  }
270
- /**
271
- * Incrementa el contador de mensajes no leídos
272
- */
273
- async incrementUnreadCount(chatId) {
274
- await this.client.send(new client_dynamodb_1.UpdateItemCommand({
275
- TableName: this.tableName,
276
- Key: (0, util_dynamodb_1.marshall)({
277
- PK: `CHAT#${chatId}`,
278
- SK: 'METADATA',
279
- }),
280
- UpdateExpression: 'SET unreadCount = if_not_exists(unreadCount, :zero) + :inc',
281
- ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
282
- ':inc': 1,
283
- ':zero': 0,
284
- }),
285
- }));
286
- }
287
272
  /**
288
273
  * Marca todos los mensajes como leídos
289
274
  */
@@ -1,8 +1,9 @@
1
- import type { SendTextParams, SendTemplateParams, SendInteractiveParams, SendInteractiveListParams, SendImageParams, SendDocumentParams, SendLocationParams, SetTypingParams, MarkAsReadParams } from './types';
1
+ import type { SendTextParams, SendTemplateParams, SendInteractiveParams, SendInteractiveListParams, SendImageParams, SendDocumentParams, SendLocationParams, SetTypingParams, MarkAsReadParams, SendAudioParams, SendVideoParams, MediaInfoResponse, CreateTemplateParams } from "./types";
2
2
  export declare class FacebookApi {
3
3
  private static token;
4
4
  private static phoneNumberId;
5
5
  private static apiVersion;
6
+ constructor(token?: string, phoneNumberId?: string, apiVersion?: string);
6
7
  private static init;
7
8
  private static post;
8
9
  static sendText(params: SendTextParams): Promise<any>;
@@ -11,7 +12,15 @@ export declare class FacebookApi {
11
12
  static sendInteractiveList(params: SendInteractiveListParams): Promise<any>;
12
13
  static sendImage(params: SendImageParams): Promise<any>;
13
14
  static sendDocument(params: SendDocumentParams): Promise<any>;
15
+ static sendAudio(params: SendAudioParams): Promise<any>;
16
+ static sendVideo(params: SendVideoParams): Promise<any>;
14
17
  static sendLocation(params: SendLocationParams): Promise<any>;
15
18
  static setTyping(params: SetTypingParams): Promise<any>;
16
19
  static markAsRead(params: MarkAsReadParams): Promise<any>;
20
+ static getMediaInfo(mediaId: string): Promise<MediaInfoResponse>;
21
+ static downloadMedia(mediaId: string): Promise<Buffer>;
22
+ static buildGoogleMapsUrl(latitude: number, longitude: number): string;
23
+ static listTemplates(wabaId: string): Promise<any>;
24
+ static createTemplate(params: CreateTemplateParams): Promise<any>;
25
+ static deleteTemplate(wabaId: string, name: string): Promise<any>;
17
26
  }
@@ -3,12 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FacebookApi = void 0;
4
4
  const http_1 = require("./http");
5
5
  class FacebookApi {
6
+ constructor(token, phoneNumberId, apiVersion) {
7
+ if (token)
8
+ FacebookApi.token = token;
9
+ if (phoneNumberId)
10
+ FacebookApi.phoneNumberId = phoneNumberId;
11
+ if (apiVersion)
12
+ FacebookApi.apiVersion = apiVersion;
13
+ }
6
14
  static async init() {
7
15
  if (this.token && this.phoneNumberId && this.apiVersion)
8
16
  return;
9
- this.token = process.env.FACEBOOK_API_TOKEN || null;
10
- this.phoneNumberId = process.env.FACEBOOK_PHONE_NUMBER_ID || null;
11
- this.apiVersion = process.env.FACEBOOK_API_VERSION || 'v18.0';
17
+ this.token = this.token || process.env.FACEBOOK_API_TOKEN || null;
18
+ this.phoneNumberId =
19
+ this.phoneNumberId || process.env.FACEBOOK_PHONE_NUMBER_ID || null;
20
+ this.apiVersion =
21
+ this.apiVersion || process.env.FACEBOOK_API_VERSION || "v18.0";
12
22
  if (!this.token || !this.phoneNumberId) {
13
23
  throw new Error("Facebook API environment variables are not properly configured (FACEBOOK_API_TOKEN, FACEBOOK_PHONE_NUMBER_ID)");
14
24
  }
@@ -37,16 +47,25 @@ class FacebookApi {
37
47
  throw new Error(msg);
38
48
  }
39
49
  }
50
+ /*
51
+ TEXT
52
+ */
40
53
  static async sendText(params) {
41
54
  const payload = {
42
55
  messaging_product: "whatsapp",
43
56
  recipient_type: "individual",
44
57
  to: params.to,
45
58
  type: "text",
46
- text: { body: params.body, preview_url: params.previewUrl ?? true },
59
+ text: {
60
+ body: params.body,
61
+ preview_url: params.previewUrl ?? true,
62
+ },
47
63
  };
48
64
  return await this.post("messages", payload);
49
65
  }
66
+ /*
67
+ TEMPLATE
68
+ */
50
69
  static async sendTemplate(params) {
51
70
  const payload = {
52
71
  messaging_product: "whatsapp",
@@ -60,6 +79,9 @@ class FacebookApi {
60
79
  };
61
80
  return await this.post("messages", payload);
62
81
  }
82
+ /*
83
+ BUTTONS
84
+ */
63
85
  static async sendInteractiveButtons(params) {
64
86
  const payload = {
65
87
  messaging_product: "whatsapp",
@@ -80,6 +102,9 @@ class FacebookApi {
80
102
  };
81
103
  return await this.post("messages", payload);
82
104
  }
105
+ /*
106
+ LIST
107
+ */
83
108
  static async sendInteractiveList(params) {
84
109
  const payload = {
85
110
  messaging_product: "whatsapp",
@@ -109,6 +134,9 @@ class FacebookApi {
109
134
  }
110
135
  return await this.post("messages", payload);
111
136
  }
137
+ /*
138
+ IMAGE
139
+ */
112
140
  static async sendImage(params) {
113
141
  const payload = {
114
142
  messaging_product: "whatsapp",
@@ -118,6 +146,9 @@ class FacebookApi {
118
146
  };
119
147
  return await this.post("messages", payload);
120
148
  }
149
+ /*
150
+ DOCUMENT
151
+ */
121
152
  static async sendDocument(params) {
122
153
  const payload = {
123
154
  messaging_product: "whatsapp",
@@ -131,6 +162,38 @@ class FacebookApi {
131
162
  };
132
163
  return await this.post("messages", payload);
133
164
  }
165
+ /*
166
+ AUDIO (NEW)
167
+ */
168
+ static async sendAudio(params) {
169
+ const payload = {
170
+ messaging_product: "whatsapp",
171
+ to: params.to,
172
+ type: "audio",
173
+ audio: {
174
+ link: params.link,
175
+ },
176
+ };
177
+ return await this.post("messages", payload);
178
+ }
179
+ /*
180
+ VIDEO (NEW)
181
+ */
182
+ static async sendVideo(params) {
183
+ const payload = {
184
+ messaging_product: "whatsapp",
185
+ to: params.to,
186
+ type: "video",
187
+ video: {
188
+ link: params.link,
189
+ caption: params.caption,
190
+ },
191
+ };
192
+ return await this.post("messages", payload);
193
+ }
194
+ /*
195
+ LOCATION
196
+ */
134
197
  static async sendLocation(params) {
135
198
  const payload = {
136
199
  messaging_product: "whatsapp",
@@ -145,6 +208,9 @@ class FacebookApi {
145
208
  };
146
209
  return await this.post("messages", payload);
147
210
  }
211
+ /*
212
+ TYPING
213
+ */
148
214
  static async setTyping(params) {
149
215
  const payload = {
150
216
  messaging_product: "whatsapp",
@@ -154,6 +220,9 @@ class FacebookApi {
154
220
  };
155
221
  return await this.post("messages", payload);
156
222
  }
223
+ /*
224
+ READ
225
+ */
157
226
  static async markAsRead(params) {
158
227
  const payload = {
159
228
  messaging_product: "whatsapp",
@@ -162,6 +231,68 @@ class FacebookApi {
162
231
  };
163
232
  return await this.post("messages", payload);
164
233
  }
234
+ /*
235
+ MEDIA INFO (NEW)
236
+ */
237
+ static async getMediaInfo(mediaId) {
238
+ await this.init();
239
+ const http = (0, http_1.getHttpClient)(this.token);
240
+ const url = `https://graph.facebook.com/${this.apiVersion}/${mediaId}`;
241
+ const { data } = await http.get(url);
242
+ return data;
243
+ }
244
+ /*
245
+ DOWNLOAD MEDIA (NEW)
246
+ */
247
+ static async downloadMedia(mediaId) {
248
+ await this.init();
249
+ const media = await this.getMediaInfo(mediaId);
250
+ const http = (0, http_1.getHttpClient)(this.token);
251
+ const { data } = await http.get(media.url, {
252
+ responseType: "arraybuffer",
253
+ });
254
+ return data;
255
+ }
256
+ /*
257
+ GOOGLE MAP URL (NEW)
258
+ */
259
+ static buildGoogleMapsUrl(latitude, longitude) {
260
+ return `https://www.google.com/maps?q=${latitude},${longitude}`;
261
+ }
262
+ static async listTemplates(wabaId) {
263
+ await this.init();
264
+ const http = (0, http_1.getHttpClient)(this.token);
265
+ const url = `https://graph.facebook.com/${this.apiVersion}/${wabaId}/message_templates`;
266
+ const { data } = await http.get(url);
267
+ return data;
268
+ }
269
+ static async createTemplate(params) {
270
+ await this.init();
271
+ const http = (0, http_1.getHttpClient)(this.token);
272
+ const url = `https://graph.facebook.com/${this.apiVersion}/${params.wabaId}/message_templates`;
273
+ const payload = {
274
+ name: params.name,
275
+ category: params.category,
276
+ language: params.language,
277
+ components: [
278
+ {
279
+ type: "BODY",
280
+ text: params.bodyText
281
+ }
282
+ ]
283
+ };
284
+ const { data } = await http.post(url, payload);
285
+ return data;
286
+ }
287
+ static async deleteTemplate(wabaId, name) {
288
+ await this.init();
289
+ const http = (0, http_1.getHttpClient)(this.token);
290
+ const url = `https://graph.facebook.com/${this.apiVersion}/${wabaId}/message_templates`;
291
+ const { data } = await http.delete(url, {
292
+ params: { name }
293
+ });
294
+ return data;
295
+ }
165
296
  }
166
297
  exports.FacebookApi = FacebookApi;
167
298
  FacebookApi.token = null;
@@ -7,7 +7,13 @@ export interface SendTemplateParams {
7
7
  to: string;
8
8
  name: string;
9
9
  languageCode: string;
10
- components?: any[];
10
+ components?: Array<{
11
+ type: "header" | "body" | "button";
12
+ parameters?: Array<{
13
+ type: "text" | "image" | "document" | "video";
14
+ text?: string;
15
+ }>;
16
+ }>;
11
17
  }
12
18
  export interface SendInteractiveParams {
13
19
  to: string;
@@ -107,3 +113,25 @@ export interface PhoneNumberInfo {
107
113
  quality_rating?: string;
108
114
  code_verification_status?: string;
109
115
  }
116
+ export interface SendAudioParams {
117
+ to: string;
118
+ link: string;
119
+ }
120
+ export interface SendVideoParams {
121
+ to: string;
122
+ link: string;
123
+ caption?: string;
124
+ }
125
+ export interface MediaInfoResponse {
126
+ url: string;
127
+ mime_type: string;
128
+ sha256: string;
129
+ file_size: number;
130
+ }
131
+ export interface CreateTemplateParams {
132
+ wabaId: string;
133
+ name: string;
134
+ category: "MARKETING" | "UTILITY" | "AUTHENTICATION";
135
+ language: string;
136
+ bodyText: string;
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rempays/shared-core",
3
- "version": "1.0.2-beta.11",
3
+ "version": "1.0.2-beta.13",
4
4
  "description": "Core utilities layer for RemPays platform with AWS services integration (Cognito, S3, Secrets Manager, Textract, Facebook API, DynamoDB)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,8 @@
38
38
  "build": "tsc",
39
39
  "build:watch": "tsc --watch",
40
40
  "prepublishOnly": "npm run build",
41
- "dev": "tsc --watch"
41
+ "dev": "tsc --watch",
42
+ "test:chat": "tsx examples/test-chat-service.ts"
42
43
  },
43
44
  "dependencies": {
44
45
  "@aws-sdk/client-cognito-identity-provider": "^3.454.0",
@@ -50,6 +51,8 @@
50
51
  },
51
52
  "devDependencies": {
52
53
  "@types/node": "^20.11.0",
54
+ "dotenv": "^17.3.1",
55
+ "tsx": "^4.7.0",
53
56
  "typescript": "^5.3.0"
54
57
  },
55
58
  "files": [