core-services-sdk 1.3.48 → 1.3.50

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.
@@ -0,0 +1,277 @@
1
+ // @ts-nocheck
2
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
3
+
4
+ import {
5
+ whatsappApis,
6
+ getWhatsAppApiUrls,
7
+ getWhatsAppMediaInfo,
8
+ downloadWhatsAppMedia,
9
+ } from '../../src/instant-messages/whatsapp-apis/whatsapp-apis.js'
10
+
11
+ import * as httpModule from '../../src/http/http.js'
12
+
13
+ global.fetch = vi.fn()
14
+
15
+ describe('getWhatsAppMediaInfo', () => {
16
+ beforeEach(() => {
17
+ fetch.mockReset()
18
+ })
19
+
20
+ it('returns media metadata correctly', async () => {
21
+ const mockResponse = {
22
+ url: 'https://cdn.whatsapp.com/file.jpg',
23
+ mime_type: 'image/jpeg',
24
+ id: 'ABC123',
25
+ }
26
+
27
+ fetch.mockResolvedValue({
28
+ ok: true,
29
+ json: async () => mockResponse,
30
+ })
31
+
32
+ const result = await getWhatsAppMediaInfo({
33
+ mediaId: 'ABC123',
34
+ token: 'TOKEN',
35
+ })
36
+
37
+ expect(fetch).toHaveBeenCalledWith(
38
+ 'https://graph.facebook.com/v21.0/ABC123',
39
+ {
40
+ headers: {
41
+ Authorization: 'Bearer TOKEN',
42
+ },
43
+ },
44
+ )
45
+
46
+ expect(result).toEqual(mockResponse)
47
+ })
48
+
49
+ it('throws on non successful response', async () => {
50
+ fetch.mockResolvedValue({
51
+ ok: false,
52
+ status: 403,
53
+ })
54
+
55
+ await expect(
56
+ getWhatsAppMediaInfo({
57
+ mediaId: 'MEDIA_ID',
58
+ token: 'TOKEN',
59
+ }),
60
+ ).rejects.toThrow('Failed to retrieve media info: 403')
61
+ })
62
+ })
63
+
64
+ describe('downloadWhatsAppMedia', () => {
65
+ beforeEach(() => {
66
+ fetch.mockReset()
67
+ })
68
+
69
+ it('downloads media as buffer', async () => {
70
+ const mockMetadata = {
71
+ url: 'https://cdn.whatsapp.com/file.jpg',
72
+ }
73
+
74
+ const mockBinary = new Uint8Array([10, 20, 30])
75
+
76
+ fetch
77
+ .mockResolvedValueOnce({
78
+ ok: true,
79
+ json: async () => mockMetadata,
80
+ })
81
+ .mockResolvedValueOnce({
82
+ ok: true,
83
+ arrayBuffer: async () => mockBinary.buffer,
84
+ })
85
+
86
+ const buffer = await downloadWhatsAppMedia({
87
+ token: 'TOKEN',
88
+ mediaId: 'MEDIA_ID',
89
+ mode: 'buffer',
90
+ })
91
+
92
+ expect(Buffer.isBuffer(buffer)).toBe(true)
93
+ expect(buffer.equals(Buffer.from(mockBinary))).toBe(true)
94
+ })
95
+
96
+ it('returns stream when mode=stream', async () => {
97
+ const mockStream = { fake: 'stream' }
98
+
99
+ fetch
100
+ .mockResolvedValueOnce({
101
+ ok: true,
102
+ json: async () => ({ url: 'http://cdn.com/x' }),
103
+ })
104
+ .mockResolvedValueOnce({
105
+ ok: true,
106
+ body: mockStream,
107
+ })
108
+
109
+ const stream = await downloadWhatsAppMedia({
110
+ token: 'TOKEN',
111
+ mediaId: 'ID',
112
+ mode: 'stream',
113
+ })
114
+
115
+ expect(stream).toBe(mockStream)
116
+ })
117
+ })
118
+
119
+ describe('getWhatsAppApiUrls', () => {
120
+ it('generates correct messages URL', () => {
121
+ const url = getWhatsAppApiUrls({
122
+ phoneNumberId: '12345',
123
+ version: 'v21.0',
124
+ })
125
+
126
+ expect(url).toBe('https://graph.facebook.com/v21.0/12345/messages')
127
+ })
128
+
129
+ it('supports custom base URL', () => {
130
+ const url = getWhatsAppApiUrls({
131
+ phoneNumberId: '99',
132
+ baseUrl: 'http://localhost:3000',
133
+ version: 'v21.0',
134
+ })
135
+
136
+ expect(url).toBe('http://localhost:3000/v21.0/99/messages')
137
+ })
138
+ })
139
+
140
+ describe('whatsappApis', () => {
141
+ let postMock
142
+
143
+ beforeEach(() => {
144
+ postMock = vi
145
+ .spyOn(httpModule, 'post')
146
+ .mockResolvedValue({ ok: true, data: 'mock-response' })
147
+ })
148
+
149
+ const token = 'TOKEN'
150
+ const phoneNumberId = '111'
151
+
152
+ const baseUrl = 'https://graph.facebook.com/v21.0/111/messages'
153
+
154
+ it('sendMessage sends correct payload', async () => {
155
+ const api = whatsappApis({ token, phoneNumberId })
156
+
157
+ await api.sendMessage({
158
+ chatId: '9725000000',
159
+ text: 'Hello',
160
+ })
161
+
162
+ expect(postMock).toHaveBeenCalledWith({
163
+ url: baseUrl,
164
+ headers: { Authorization: 'Bearer TOKEN' },
165
+ body: {
166
+ recipient_type: 'individual',
167
+ messaging_product: 'whatsapp',
168
+ to: '9725000000',
169
+ type: 'text',
170
+ text: {
171
+ preview_url: true,
172
+ body: 'Hello',
173
+ },
174
+ },
175
+ })
176
+ })
177
+
178
+ it('sendPhoto sends correct body', async () => {
179
+ const api = whatsappApis({ token, phoneNumberId })
180
+
181
+ await api.sendPhoto({
182
+ chatId: '1',
183
+ photo: 'http://img.jpg',
184
+ caption: 'Hi',
185
+ })
186
+
187
+ expect(postMock).toHaveBeenCalledWith({
188
+ url: baseUrl,
189
+ headers: { Authorization: 'Bearer TOKEN' },
190
+ body: {
191
+ recipient_type: 'individual',
192
+ messaging_product: 'whatsapp',
193
+ to: '1',
194
+ type: 'image',
195
+ image: {
196
+ link: 'http://img.jpg',
197
+ caption: 'Hi',
198
+ },
199
+ },
200
+ })
201
+ })
202
+
203
+ it('sendVideo sends filename when provided', async () => {
204
+ const api = whatsappApis({ token, phoneNumberId })
205
+
206
+ await api.sendVideo({
207
+ chatId: '77',
208
+ video: 'http://video.mp4',
209
+ caption: 'watch',
210
+ filename: 'clip.mp4',
211
+ })
212
+
213
+ expect(postMock).toHaveBeenCalledWith({
214
+ url: baseUrl,
215
+ headers: { Authorization: 'Bearer TOKEN' },
216
+ body: {
217
+ recipient_type: 'individual',
218
+ messaging_product: 'whatsapp',
219
+ to: '77',
220
+ type: 'video',
221
+ video: {
222
+ link: 'http://video.mp4',
223
+ caption: 'watch',
224
+ filename: 'clip.mp4',
225
+ },
226
+ },
227
+ })
228
+ })
229
+
230
+ it('sendDocument works without filename', async () => {
231
+ const api = whatsappApis({ token, phoneNumberId })
232
+
233
+ await api.sendDocument({
234
+ chatId: '33',
235
+ document: 'http://file.pdf',
236
+ caption: 'doc',
237
+ })
238
+
239
+ expect(postMock).toHaveBeenCalledWith({
240
+ url: baseUrl,
241
+ headers: { Authorization: 'Bearer TOKEN' },
242
+ body: {
243
+ recipient_type: 'individual',
244
+ messaging_product: 'whatsapp',
245
+ to: '33',
246
+ type: 'document',
247
+ document: {
248
+ link: 'http://file.pdf',
249
+ caption: 'doc',
250
+ },
251
+ },
252
+ })
253
+ })
254
+
255
+ it('sendAudio sends correct structure', async () => {
256
+ const api = whatsappApis({ token, phoneNumberId })
257
+
258
+ await api.sendAudio({
259
+ chatId: '555',
260
+ audio: 'http://a.mp3',
261
+ })
262
+
263
+ expect(postMock).toHaveBeenCalledWith({
264
+ url: baseUrl,
265
+ headers: { Authorization: 'Bearer TOKEN' },
266
+ body: {
267
+ recipient_type: 'individual',
268
+ messaging_product: 'whatsapp',
269
+ to: '555',
270
+ type: 'audio',
271
+ audio: {
272
+ link: 'http://a.mp3',
273
+ },
274
+ },
275
+ })
276
+ })
277
+ })
@@ -2,3 +2,5 @@ export * from './im-platform.js'
2
2
  export * from './message-type.js'
3
3
  export * from './message-types.js'
4
4
  export * from './message-unified-mapper.js'
5
+ export * from './telegram-apis/telegram-apis.js'
6
+ export * from './whatsapp-apis/whatsapp-apis.js'
@@ -2,37 +2,64 @@
2
2
  * *
3
3
  */
4
4
  export type MESSAGE_MEDIA_TYPE = string
5
- export namespace MESSAGE_MEDIA_TYPE {
6
- let TEXT: string
7
- let POLL: string
8
- let VIDEO: string
9
- let PHOTO: string
10
- let IMAGE: string
11
- let VOICE: string
12
- let AUDIO: string
13
- let STICKER: string
14
- let CONTACT: string
15
- let MESSAGE: string
16
- let REACTION: string
17
- let DOCUMENT: string
18
- let LOCATION: string
19
- let CONTACTS: string
20
- let VIDEO_NOTE: string
21
- let BUTTON_CLICK: string
22
- let BUTTON_CLICK_MULTIPLE: string
5
+ /**
6
+ * Enumerates all supported incoming media/content types
7
+ * across messaging platforms (Telegram, WhatsApp, etc).
8
+ *
9
+ * This is the unified taxonomy used inside the system
10
+ * after normalization of the raw message payload.
11
+ *
12
+ * @readonly
13
+ * @enum {string}
14
+ * @type {{ [key: string]: string }}
15
+ *
16
+ * @property {"text"} TEXT
17
+ * @property {"poll"} POLL
18
+ * @property {"video"} VIDEO
19
+ * @property {"photo"} PHOTO
20
+ * @property {"image"} IMAGE
21
+ * @property {"voice"} VOICE
22
+ * @property {"audio"} AUDIO
23
+ * @property {"sticker"} STICKER
24
+ * @property {"contact"} CONTACT
25
+ * @property {"reaction"} REACTION
26
+ * @property {"document"} DOCUMENT
27
+ * @property {"location"} LOCATION
28
+ * @property {"contacts"} CONTACTS
29
+ * @property {"video_note"} VIDEO_NOTE
30
+ * @property {"button_click"} BUTTON_CLICK
31
+ * @property {"button_click_multiple"} BUTTON_CLICK_MULTIPLE
32
+ */
33
+ export const MESSAGE_MEDIA_TYPE: {
34
+ [key: string]: string
23
35
  }
24
36
  /**
25
37
  * *
26
38
  */
27
39
  export type MESSAGE_TYPE = string
28
- export namespace MESSAGE_TYPE {
29
- let MESSAGE_1: string
30
- export { MESSAGE_1 as MESSAGE }
31
- import BUTTON_CLICK_1 = MESSAGE_MEDIA_TYPE.BUTTON_CLICK
32
- export { BUTTON_CLICK_1 as BUTTON_CLICK }
33
- import BUTTON_CLICK_MULTIPLE_1 = MESSAGE_MEDIA_TYPE.BUTTON_CLICK_MULTIPLE
34
- export { BUTTON_CLICK_MULTIPLE_1 as BUTTON_CLICK_MULTIPLE }
35
- export let UNKNOWN_MESSAGE_TYPE: string
40
+ /**
41
+ * Additional high-level message categories.
42
+ *
43
+ * These represent logical groupings rather than raw media types.
44
+ *
45
+ * @readonly
46
+ * @enum {string}
47
+ * @type {{ [key: string]: string }}
48
+ *
49
+ * @property {"message"} MESSAGE
50
+ * Regular message container (base type in some providers).
51
+ *
52
+ * @property {"button_click"} BUTTON_CLICK
53
+ * A click on a single interactive button.
54
+ *
55
+ * @property {"button_click_multiple"} BUTTON_CLICK_MULTIPLE
56
+ * A selection from a list of interactive reply choices.
57
+ *
58
+ * @property {"unknown_message_type"} UNKNOWN_MESSAGE_TYPE
59
+ * Used when the system cannot identify or normalize the message type.
60
+ */
61
+ export const MESSAGE_TYPE: {
62
+ [key: string]: string
36
63
  }
37
64
  /**
38
65
  * *
@@ -60,3 +87,24 @@ export const MESSAGE_MEDIA_TYPE_MAPPER: {
60
87
  [MESSAGE_MEDIA_TYPE.PHOTO]: string
61
88
  [MESSAGE_MEDIA_TYPE.CONTACTS]: string
62
89
  }
90
+ /**
91
+ * *
92
+ */
93
+ export type UNIFIED_MESSAGE_MEDIA_TYPE = string
94
+ /**
95
+ * Unified message media types based on existing MESSAGE_MEDIA_TYPE and MESSAGE_TYPE.
96
+ *
97
+ * This enum flattens and merges all raw message media types
98
+ * into a single canonical type list.
99
+ *
100
+ * VOICE → AUDIO
101
+ * PHOTO → IMAGE
102
+ * CONTACTS → CONTACT
103
+ *
104
+ * @readonly
105
+ * @enum {string}
106
+ * @type {{ [key: string]: string }}
107
+ */
108
+ export const UNIFIED_MESSAGE_MEDIA_TYPE: {
109
+ [key: string]: string
110
+ }
@@ -0,0 +1,105 @@
1
+ export function getTelegramApiUrls({
2
+ token,
3
+ telegramBaseUrl,
4
+ }: {
5
+ token: string
6
+ telegramBaseUrl?: string
7
+ }): TelegramApiUrls
8
+ export function telegramApis({ token }: { token: string }): TelegramApis
9
+ /**
10
+ * Builds a full set of Telegram Bot API endpoint URLs
11
+ * based on the provided bot token and an optional base URL.
12
+ *
13
+ * This helper centralizes all Telegram endpoints used by the system,
14
+ * making it easier to mock, override, or customize for testing environments.
15
+ */
16
+ export type TelegramApiUrls = {
17
+ /**
18
+ * URL to send a text message to a chat using the Telegram Bot API.
19
+ */
20
+ SEND_MESSAGE: string
21
+ /**
22
+ * URL to forward an existing message from one chat to another.
23
+ */
24
+ FORWARD_MESSAGE: string
25
+ /**
26
+ * URL to send a photo to a chat.
27
+ */
28
+ SEND_PHOTO: string
29
+ /**
30
+ * URL to send an audio file.
31
+ */
32
+ SEND_AUDIO: string
33
+ /**
34
+ * URL to send files such as PDF, DOC, ZIP, and others supported by Telegram.
35
+ */
36
+ SEND_DOCUMENT: string
37
+ /**
38
+ * URL to send a sticker.
39
+ */
40
+ SEND_STICKER: string
41
+ /**
42
+ * URL to send a video file.
43
+ */
44
+ SEND_VIDEO: string
45
+ /**
46
+ * URL to send a voice note.
47
+ */
48
+ SEND_VOICE: string
49
+ /**
50
+ * URL to send a geolocation point.
51
+ */
52
+ SEND_LOCATION: string
53
+ /**
54
+ * URL to send a chat action (typing, uploading photo, etc).
55
+ */
56
+ SEND_CHAT_ACTION: string
57
+ /**
58
+ * URL to retrieve the profile photos of a specific user.
59
+ */
60
+ GET_USER_PROFILE_PHOTOS: string
61
+ /**
62
+ * URL to poll for new updates (not used when using webhooks).
63
+ */
64
+ GET_UPDATES: string
65
+ /**
66
+ * URL to fetch a file path for downloading a file uploaded to Telegram servers.
67
+ */
68
+ GET_FILE: string
69
+ }
70
+ /**
71
+ * Factory that creates a set of high level Telegram Bot API helper methods.
72
+ *
73
+ * Each method sends a specific type of message (text, photo, video, document)
74
+ * through the Telegram Bot API using the provided bot token.
75
+ *
76
+ * This abstraction wraps the raw URL generation logic and HTTP calls,
77
+ * allowing higher level services to use clean method calls instead of
78
+ * managing endpoint URLs manually.
79
+ */
80
+ export type TelegramApis = {
81
+ /**
82
+ * Sends a text message to a specific chat.
83
+ */
84
+ sendMessage: Function
85
+ /**
86
+ * Sends a text message with an inline keyboard button group.
87
+ */
88
+ sendButtonsGroup: Function
89
+ /**
90
+ * Sends a photo with an optional caption.
91
+ */
92
+ sendPhoto: Function
93
+ /**
94
+ * Sends a video with an optional caption.
95
+ */
96
+ sendVideo: Function
97
+ /**
98
+ * Sends an audio file with an optional caption.
99
+ */
100
+ sendAudio: Function
101
+ /**
102
+ * Sends a document file with an optional caption.
103
+ */
104
+ sendDocument: Function
105
+ }
@@ -0,0 +1,73 @@
1
+ export function getWhatsAppMediaInfo({
2
+ token,
3
+ mediaId,
4
+ version,
5
+ baseUrl,
6
+ }: {
7
+ mediaId: string
8
+ token: string
9
+ version?: string
10
+ }): Promise<any>
11
+ export function downloadWhatsAppMedia({
12
+ token,
13
+ mediaId,
14
+ mode,
15
+ }: {
16
+ mediaId: string
17
+ token: string
18
+ mode: 'buffer' | 'stream'
19
+ }): Promise<Buffer | ReadableStream>
20
+ export function getWhatsAppApiUrls({
21
+ phoneNumberId,
22
+ version,
23
+ baseUrl,
24
+ }: {
25
+ phoneNumberId: string
26
+ version?: string
27
+ baseUrl?: string
28
+ }): string
29
+ export function whatsappApis({
30
+ token,
31
+ phoneNumberId,
32
+ version,
33
+ }: {
34
+ token: string
35
+ phoneNumberId: string
36
+ version?: string
37
+ }): WhatsAppApis
38
+ /**
39
+ * Factory that creates WhatsApp Cloud API helper methods.
40
+ *
41
+ * This module wraps the WhatsApp Graph API endpoints and exposes
42
+ * high level functions for sending text messages, interactive buttons,
43
+ * images, videos, documents, and audio files.
44
+ *
45
+ * Each returned method builds the correct request format according
46
+ * to the WhatsApp Cloud API specification.
47
+ */
48
+ export type WhatsAppApis = {
49
+ /**
50
+ * Sends a plain text message to an individual WhatsApp user.
51
+ */
52
+ sendMessage: Function
53
+ /**
54
+ * Sends an interactive message containing buttons.
55
+ */
56
+ sendButtonsGroup: Function
57
+ /**
58
+ * Sends an image message using a public URL.
59
+ */
60
+ sendPhoto: Function
61
+ /**
62
+ * Sends a video file using a public URL.
63
+ */
64
+ sendVideo: Function
65
+ /**
66
+ * Sends a document message using a public URL.
67
+ */
68
+ sendDocument: Function
69
+ /**
70
+ * Sends an audio file using a public URL.
71
+ */
72
+ sendAudio: Function
73
+ }