@wabot-dev/framework 0.5.7 → 0.5.9
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.
- package/dist/src/addon/chat-controller/cmd/CmdChannel.js +6 -2
- package/dist/src/addon/chat-controller/cmd/cmdChannelName.js +3 -0
- package/dist/src/addon/chat-controller/socket/SocketChannel.js +6 -2
- package/dist/src/addon/chat-controller/socket/socketChannelName.js +3 -0
- package/dist/src/addon/chat-controller/telegram/TelegramChannel.js +6 -2
- package/dist/src/addon/chat-controller/telegram/telegramChannelName.js +3 -0
- package/dist/src/addon/chat-controller/wasender/@whatsAppByWasender.js +20 -0
- package/dist/src/addon/chat-controller/wasender/WasenderWebhookController.js +82 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppByWasenderChannel.js +67 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppByWasenderChannelConfig.js +16 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppReceiverByWasender.js +39 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppSenderByWasender.js +33 -0
- package/dist/src/addon/chat-controller/wasender/extractNumberFromWasenderKey.js +5 -0
- package/dist/src/addon/chat-controller/wasender/whatsAppByWasenderChannelName.js +3 -0
- package/dist/src/addon/chat-controller/whatsapp/WhatsAppChannel.js +3 -0
- package/dist/src/addon/chat-controller/whatsapp/cloud-api/WhatsAppReceiverByCloudApi.js +2 -2
- package/dist/src/addon/chat-controller/whatsapp/proxy/WhatsAppReceiverByWabotProxy.js +2 -1
- package/dist/src/addon/chat-controller/whatsapp/whatsAppChannelName.js +3 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/@whatsAppByWasender.js +20 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppByWasenderChannel.js +52 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppByWasenderChannelConfig.js +16 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppReceiverByWasender.js +106 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppSenderByWasender.js +40 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/extractNumberFromWasenderKey.js +5 -0
- package/dist/src/feature/rest-controller/metadata/RestControllerMetadataStore.js +35 -8
- package/dist/src/index.d.ts +203 -7
- package/dist/src/index.js +12 -0
- package/dist/src/node_modules/wasenderapi/dist/index.js +553 -0
- package/package.json +3 -2
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var WasenderAPIError = class _WasenderAPIError extends Error {
|
|
3
|
+
constructor(apiMessage, statusCode, errorDetails, retryAfter, rateLimit) {
|
|
4
|
+
super(`Wasender API Error (Status ${statusCode || "N/A"}): ${apiMessage}`);
|
|
5
|
+
this.success = false;
|
|
6
|
+
this.name = "WasenderAPIError";
|
|
7
|
+
this.apiMessage = apiMessage;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
this.errorDetails = errorDetails;
|
|
10
|
+
this.retryAfter = retryAfter;
|
|
11
|
+
this.rateLimit = rateLimit;
|
|
12
|
+
Object.setPrototypeOf(this, _WasenderAPIError.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/webhook.ts
|
|
17
|
+
var WEBHOOK_SIGNATURE_HEADER = "x-webhook-signature";
|
|
18
|
+
function verifyWasenderWebhookSignature(requestSignature, configuredSecret) {
|
|
19
|
+
if (!requestSignature || !configuredSecret) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return requestSignature === configuredSecret;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/main.ts
|
|
26
|
+
var SDK_VERSION = "0.1.0";
|
|
27
|
+
var Wasender = class {
|
|
28
|
+
constructor(apiKey, personalAccessToken, baseUrl = "https://www.wasenderapi.com/api", fetchImplementation, retryOptions, webhookSecret) {
|
|
29
|
+
if (apiKey === void 0 && personalAccessToken === void 0) {
|
|
30
|
+
throw new Error("Either an API Key (for session operations) or a Personal Access Token (for session management) must be provided to initialize the Wasender SDK.");
|
|
31
|
+
}
|
|
32
|
+
this.apiKey = apiKey;
|
|
33
|
+
this.personalAccessToken = personalAccessToken;
|
|
34
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
35
|
+
this.fetchImpl = fetchImplementation || globalThis.fetch;
|
|
36
|
+
this.retryConfig = {
|
|
37
|
+
enabled: retryOptions?.enabled ?? false,
|
|
38
|
+
maxRetries: retryOptions?.maxRetries ?? 0
|
|
39
|
+
};
|
|
40
|
+
this.configuredWebhookSecret = webhookSecret;
|
|
41
|
+
if (!this.fetchImpl) {
|
|
42
|
+
throw new Error("Fetch implementation is not available. Please provide one (e.g., for Node.js < 18 by polyfilling globalThis.fetch or passing a custom fetch).");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
parseRateLimitHeaders(headers) {
|
|
46
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
47
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
48
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
49
|
+
const resetTimestampNum = reset ? parseInt(reset, 10) : null;
|
|
50
|
+
return {
|
|
51
|
+
limit: limit ? parseInt(limit, 10) : null,
|
|
52
|
+
remaining: remaining ? parseInt(remaining, 10) : null,
|
|
53
|
+
resetTimestamp: resetTimestampNum,
|
|
54
|
+
getResetTimestampAsDate: () => resetTimestampNum ? new Date(resetTimestampNum * 1e3) : null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// General purpose request helper
|
|
58
|
+
async request(method, path, body) {
|
|
59
|
+
const url = `${this.baseUrl}${path}`;
|
|
60
|
+
const isSessionManagementPath = path.startsWith("/whatsapp-sessions") || path === "/status";
|
|
61
|
+
let tokenToUse;
|
|
62
|
+
if (isSessionManagementPath) {
|
|
63
|
+
if (!this.personalAccessToken) {
|
|
64
|
+
throw new WasenderAPIError("Personal Access Token is required for this operation but was not provided during SDK initialization.", 401);
|
|
65
|
+
}
|
|
66
|
+
tokenToUse = this.personalAccessToken;
|
|
67
|
+
} else {
|
|
68
|
+
if (!this.apiKey) {
|
|
69
|
+
throw new WasenderAPIError("Session API Key is required for this operation but was not provided or is not applicable.", 401);
|
|
70
|
+
}
|
|
71
|
+
tokenToUse = this.apiKey;
|
|
72
|
+
}
|
|
73
|
+
const requestHeaders = {
|
|
74
|
+
"Authorization": `Bearer ${tokenToUse}`,
|
|
75
|
+
"Accept": "application/json",
|
|
76
|
+
"User-Agent": `wasender-typescript-sdk/${SDK_VERSION}`
|
|
77
|
+
};
|
|
78
|
+
let processedBody = body ? { ...body } : null;
|
|
79
|
+
if (processedBody && typeof processedBody === "object" && "messageType" in processedBody && processedBody.messageType === "location") {
|
|
80
|
+
const locationPayload = processedBody.location;
|
|
81
|
+
if (locationPayload && typeof locationPayload.latitude === "string") {
|
|
82
|
+
locationPayload.latitude = parseFloat(locationPayload.latitude);
|
|
83
|
+
}
|
|
84
|
+
if (locationPayload && typeof locationPayload.longitude === "string") {
|
|
85
|
+
locationPayload.longitude = parseFloat(locationPayload.longitude);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const requestOptions = {
|
|
89
|
+
method,
|
|
90
|
+
headers: requestHeaders
|
|
91
|
+
};
|
|
92
|
+
if ((method === "POST" || method === "PUT") && processedBody) {
|
|
93
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
94
|
+
requestOptions.body = JSON.stringify(processedBody);
|
|
95
|
+
} else if (method === "POST" || method === "PUT") {
|
|
96
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
97
|
+
requestOptions.body = JSON.stringify({});
|
|
98
|
+
} else ;
|
|
99
|
+
let attempts = 0;
|
|
100
|
+
while (true) {
|
|
101
|
+
attempts++;
|
|
102
|
+
let httpResponse;
|
|
103
|
+
let rateLimitInfo;
|
|
104
|
+
try {
|
|
105
|
+
httpResponse = await this.fetchImpl(url, requestOptions);
|
|
106
|
+
rateLimitInfo = this.parseRateLimitHeaders(httpResponse.headers);
|
|
107
|
+
} catch (networkError) {
|
|
108
|
+
const errorToThrow = networkError instanceof Error ? new WasenderAPIError(
|
|
109
|
+
`Network error: ${networkError.message}`,
|
|
110
|
+
void 0,
|
|
111
|
+
void 0,
|
|
112
|
+
void 0,
|
|
113
|
+
rateLimitInfo
|
|
114
|
+
) : new WasenderAPIError(
|
|
115
|
+
"An unknown network error occurred during the request.",
|
|
116
|
+
void 0,
|
|
117
|
+
void 0,
|
|
118
|
+
void 0,
|
|
119
|
+
rateLimitInfo
|
|
120
|
+
);
|
|
121
|
+
if (attempts > this.retryConfig.maxRetries || !this.retryConfig.enabled) {
|
|
122
|
+
throw errorToThrow;
|
|
123
|
+
}
|
|
124
|
+
if (attempts > 1 && method === "GET") throw errorToThrow;
|
|
125
|
+
console.warn(
|
|
126
|
+
`Wasender SDK: Network error on attempt ${attempts} for ${method} ${path}. Retrying if configured...`
|
|
127
|
+
);
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * attempts));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let responseBody;
|
|
132
|
+
try {
|
|
133
|
+
if (httpResponse.status === 204) {
|
|
134
|
+
if (path.includes("/block") || path.includes("/unblock")) {
|
|
135
|
+
responseBody = { success: true, data: { message: path.includes("/block") ? "Contact blocked" : "Contact unblocked" } };
|
|
136
|
+
} else if (method === "DELETE" && path.startsWith("/whatsapp-sessions/")) {
|
|
137
|
+
responseBody = { success: true, data: null };
|
|
138
|
+
} else {
|
|
139
|
+
responseBody = { success: true };
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
if (path === "/status") {
|
|
143
|
+
responseBody = await httpResponse.json();
|
|
144
|
+
} else if (path.includes("/regenerate-key")) {
|
|
145
|
+
responseBody = await httpResponse.json();
|
|
146
|
+
} else {
|
|
147
|
+
responseBody = await httpResponse.json();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (parseError) {
|
|
151
|
+
const errorText = await httpResponse.text().catch(() => "Could not retrieve error text.");
|
|
152
|
+
throw new WasenderAPIError(
|
|
153
|
+
`Failed to parse API response (Status ${httpResponse.status}): ${errorText}`,
|
|
154
|
+
httpResponse.status,
|
|
155
|
+
void 0,
|
|
156
|
+
void 0,
|
|
157
|
+
rateLimitInfo
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (path === "/status" && httpResponse.ok) {
|
|
161
|
+
return {
|
|
162
|
+
response: responseBody,
|
|
163
|
+
rateLimit: rateLimitInfo
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (path.includes("/regenerate-key") && "api_key" in responseBody && responseBody.success === true) {
|
|
167
|
+
return {
|
|
168
|
+
response: responseBody,
|
|
169
|
+
rateLimit: rateLimitInfo
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (!httpResponse.ok || responseBody && "success" in responseBody && typeof responseBody.success === "boolean" && !responseBody.success) {
|
|
173
|
+
const apiError = responseBody;
|
|
174
|
+
const errorToThrow = new WasenderAPIError(
|
|
175
|
+
apiError.message || "API request failed with an unspecified error.",
|
|
176
|
+
httpResponse.status,
|
|
177
|
+
apiError.errors,
|
|
178
|
+
apiError.retry_after,
|
|
179
|
+
rateLimitInfo
|
|
180
|
+
);
|
|
181
|
+
if (this.retryConfig.enabled && errorToThrow.statusCode === 429 && errorToThrow.retryAfter && errorToThrow.retryAfter > 0 && attempts <= this.retryConfig.maxRetries) {
|
|
182
|
+
console.warn(
|
|
183
|
+
`Wasender SDK: Rate limit (429) on attempt ${attempts} for ${method} ${path}. Retrying after ${errorToThrow.retryAfter}s...`
|
|
184
|
+
);
|
|
185
|
+
await new Promise((resolve) => setTimeout(resolve, errorToThrow.retryAfter * 1e3));
|
|
186
|
+
continue;
|
|
187
|
+
} else {
|
|
188
|
+
throw errorToThrow;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if ("success" in responseBody && responseBody.success === true) {
|
|
192
|
+
return {
|
|
193
|
+
response: responseBody,
|
|
194
|
+
rateLimit: rateLimitInfo
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
throw new WasenderAPIError(
|
|
198
|
+
`Unexpected API response structure from ${method} ${path}. Status: ${httpResponse.status}`,
|
|
199
|
+
httpResponse.status,
|
|
200
|
+
void 0,
|
|
201
|
+
void 0,
|
|
202
|
+
rateLimitInfo
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// End of request method
|
|
207
|
+
// General purpose POST (adapted from original post for sending messages)
|
|
208
|
+
async postInternal(path, payload) {
|
|
209
|
+
const apiPayload = payload ? { ...payload } : null;
|
|
210
|
+
if (apiPayload && "messageType" in apiPayload) {
|
|
211
|
+
delete apiPayload.messageType;
|
|
212
|
+
}
|
|
213
|
+
return this.request("POST", path, apiPayload);
|
|
214
|
+
}
|
|
215
|
+
// General purpose GET
|
|
216
|
+
async getInternal(path) {
|
|
217
|
+
return this.request("GET", path);
|
|
218
|
+
}
|
|
219
|
+
// General purpose PUT
|
|
220
|
+
async putInternal(path, payload) {
|
|
221
|
+
return this.request("PUT", path, payload);
|
|
222
|
+
}
|
|
223
|
+
// General purpose DELETE
|
|
224
|
+
async deleteInternal(path) {
|
|
225
|
+
return this.request("DELETE", path);
|
|
226
|
+
}
|
|
227
|
+
// ---------- Generic Send Method (modified to use postInternal) ----------
|
|
228
|
+
/**
|
|
229
|
+
* Sends any valid Wasender message payload.
|
|
230
|
+
* @param payload A WasenderMessagePayload object (e.g., TextOnlyMessage, ImageUrlMessage).
|
|
231
|
+
* @returns A promise that resolves to the API response and rate limit information.
|
|
232
|
+
* @throws WasenderAPIError if the request fails.
|
|
233
|
+
*/
|
|
234
|
+
async send(payload) {
|
|
235
|
+
const { messageType, ...apiPayload } = payload;
|
|
236
|
+
return this.postInternal("/send-message", apiPayload);
|
|
237
|
+
}
|
|
238
|
+
// ---------- Specific Endpoint Helpers (Wrappers for the generic send) ----------
|
|
239
|
+
// These now add the `messageType` discriminant before calling the generic send.
|
|
240
|
+
sendText(payload) {
|
|
241
|
+
return this.send({ ...payload, messageType: "text" });
|
|
242
|
+
}
|
|
243
|
+
sendImage(payload) {
|
|
244
|
+
return this.send({ ...payload, messageType: "image" });
|
|
245
|
+
}
|
|
246
|
+
sendVideo(payload) {
|
|
247
|
+
return this.send({ ...payload, messageType: "video" });
|
|
248
|
+
}
|
|
249
|
+
sendDocument(payload) {
|
|
250
|
+
return this.send({ ...payload, messageType: "document" });
|
|
251
|
+
}
|
|
252
|
+
sendAudio(payload) {
|
|
253
|
+
return this.send({ ...payload, messageType: "audio" });
|
|
254
|
+
}
|
|
255
|
+
sendSticker(payload) {
|
|
256
|
+
return this.send({ ...payload, messageType: "sticker" });
|
|
257
|
+
}
|
|
258
|
+
sendContact(payload) {
|
|
259
|
+
return this.send({ ...payload, messageType: "contact" });
|
|
260
|
+
}
|
|
261
|
+
sendLocation(payload) {
|
|
262
|
+
return this.send({ ...payload, messageType: "location" });
|
|
263
|
+
}
|
|
264
|
+
// ---------- Contact Management Methods ----------
|
|
265
|
+
/**
|
|
266
|
+
* Retrieves a list of all contacts synced with the WhatsApp session.
|
|
267
|
+
* @returns A promise that resolves to the list of contacts and rate limit information.
|
|
268
|
+
* @throws WasenderAPIError if the request fails.
|
|
269
|
+
*/
|
|
270
|
+
async getContacts() {
|
|
271
|
+
return this.getInternal("/contacts");
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Retrieves detailed information for a specific contact.
|
|
275
|
+
* @param contactPhoneNumber The JID (Jabber ID) of the contact in E.164 format (e.g., 1234567890).
|
|
276
|
+
* @returns A promise that resolves to the contact information and rate limit information.
|
|
277
|
+
* @throws WasenderAPIError if the request fails.
|
|
278
|
+
*/
|
|
279
|
+
async getContactInfo(contactPhoneNumber) {
|
|
280
|
+
if (!contactPhoneNumber) {
|
|
281
|
+
throw new WasenderAPIError("Contact phone number (JID) is required.", 400);
|
|
282
|
+
}
|
|
283
|
+
return this.getInternal(`/contacts/${contactPhoneNumber}`);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Retrieves the URL of the profile picture for a specific contact.
|
|
287
|
+
* @param contactPhoneNumber The JID (Jabber ID) of the contact in E.164 format (e.g., 1234567890).
|
|
288
|
+
* @returns A promise that resolves to the profile picture URL and rate limit information.
|
|
289
|
+
* @throws WasenderAPIError if the request fails.
|
|
290
|
+
*/
|
|
291
|
+
async getContactProfilePicture(contactPhoneNumber) {
|
|
292
|
+
if (!contactPhoneNumber) {
|
|
293
|
+
throw new WasenderAPIError("Contact phone number (JID) is required.", 400);
|
|
294
|
+
}
|
|
295
|
+
return this.getInternal(`/contacts/${contactPhoneNumber}/picture`);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Blocks a specific contact.
|
|
299
|
+
* @param contactPhoneNumber The JID (Jabber ID) of the contact in E.164 format (e.g., 1234567890).
|
|
300
|
+
* @returns A promise that resolves to the action status and rate limit information.
|
|
301
|
+
* @throws WasenderAPIError if the request fails.
|
|
302
|
+
*/
|
|
303
|
+
async blockContact(contactPhoneNumber) {
|
|
304
|
+
if (!contactPhoneNumber) {
|
|
305
|
+
throw new WasenderAPIError("Contact phone number (JID) is required.", 400);
|
|
306
|
+
}
|
|
307
|
+
return this.postInternal(`/contacts/${contactPhoneNumber}/block`, null);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Unblocks a specific contact.
|
|
311
|
+
* @param contactPhoneNumber The JID (Jabber ID) of the contact in E.164 format (e.g., 1234567890).
|
|
312
|
+
* @returns A promise that resolves to the action status and rate limit information.
|
|
313
|
+
* @throws WasenderAPIError if the request fails.
|
|
314
|
+
*/
|
|
315
|
+
async unblockContact(contactPhoneNumber) {
|
|
316
|
+
if (!contactPhoneNumber) {
|
|
317
|
+
throw new WasenderAPIError("Contact phone number (JID) is required.", 400);
|
|
318
|
+
}
|
|
319
|
+
return this.postInternal(`/contacts/${contactPhoneNumber}/unblock`, null);
|
|
320
|
+
}
|
|
321
|
+
// ---------- Group Management Methods ----------
|
|
322
|
+
/**
|
|
323
|
+
* Retrieves a list of all WhatsApp groups the connected account is a member of.
|
|
324
|
+
* @returns A promise that resolves to the list of groups and rate limit information.
|
|
325
|
+
* @throws WasenderAPIError if the request fails.
|
|
326
|
+
*/
|
|
327
|
+
async getGroups() {
|
|
328
|
+
return this.getInternal("/groups");
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Retrieves metadata for a specific group.
|
|
332
|
+
* @param groupJid The JID of the group (e.g., '123456789-987654321@g.us').
|
|
333
|
+
* @returns A promise that resolves to the group metadata and rate limit information.
|
|
334
|
+
* @throws WasenderAPIError if the request fails.
|
|
335
|
+
*/
|
|
336
|
+
async getGroupMetadata(groupJid) {
|
|
337
|
+
if (!groupJid) {
|
|
338
|
+
throw new WasenderAPIError("Group JID is required.", 400);
|
|
339
|
+
}
|
|
340
|
+
return this.getInternal(`/groups/${groupJid}/metadata`);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Retrieves a list of participants for a specific group.
|
|
344
|
+
* @param groupJid The JID of the group.
|
|
345
|
+
* @returns A promise that resolves to the list of group participants and rate limit information.
|
|
346
|
+
* @throws WasenderAPIError if the request fails.
|
|
347
|
+
*/
|
|
348
|
+
async getGroupParticipants(groupJid) {
|
|
349
|
+
if (!groupJid) {
|
|
350
|
+
throw new WasenderAPIError("Group JID is required.", 400);
|
|
351
|
+
}
|
|
352
|
+
return this.getInternal(`/groups/${groupJid}/participants`);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Adds participants to a specific group. Requires admin privileges in the group.
|
|
356
|
+
* @param groupJid The JID of the group.
|
|
357
|
+
* @param participants An array of participant JIDs (E.164 format phone numbers) to add.
|
|
358
|
+
* @returns A promise that resolves to the action status for each participant and rate limit information.
|
|
359
|
+
* @throws WasenderAPIError if the request fails.
|
|
360
|
+
*/
|
|
361
|
+
async addGroupParticipants(groupJid, participants) {
|
|
362
|
+
if (!groupJid) {
|
|
363
|
+
throw new WasenderAPIError("Group JID is required.", 400);
|
|
364
|
+
}
|
|
365
|
+
if (!participants || participants.length === 0) {
|
|
366
|
+
throw new WasenderAPIError("Participants array cannot be null or empty.", 400);
|
|
367
|
+
}
|
|
368
|
+
const payload = { participants };
|
|
369
|
+
return this.postInternal(
|
|
370
|
+
`/groups/${groupJid}/participants/add`,
|
|
371
|
+
payload
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Removes participants from a specific group. Requires admin privileges in the group.
|
|
376
|
+
* @param groupJid The JID of the group.
|
|
377
|
+
* @param participants An array of participant JIDs (E.164 format phone numbers) to remove.
|
|
378
|
+
* @returns A promise that resolves to the action status for each participant and rate limit information.
|
|
379
|
+
* @throws WasenderAPIError if the request fails.
|
|
380
|
+
*/
|
|
381
|
+
async removeGroupParticipants(groupJid, participants) {
|
|
382
|
+
if (!groupJid) {
|
|
383
|
+
throw new WasenderAPIError("Group JID is required.", 400);
|
|
384
|
+
}
|
|
385
|
+
if (!participants || participants.length === 0) {
|
|
386
|
+
throw new WasenderAPIError("Participants array cannot be null or empty.", 400);
|
|
387
|
+
}
|
|
388
|
+
const payload = { participants };
|
|
389
|
+
return this.postInternal(
|
|
390
|
+
`/groups/${groupJid}/participants/remove`,
|
|
391
|
+
payload
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Updates settings for a specific group.
|
|
396
|
+
* @param groupJid The JID of the group.
|
|
397
|
+
* @param settings An object containing the settings to update (e.g., subject, description, announce, restrict).
|
|
398
|
+
* @returns A promise that resolves to the updated group settings information and rate limit information.
|
|
399
|
+
* @throws WasenderAPIError if the request fails.
|
|
400
|
+
*/
|
|
401
|
+
async updateGroupSettings(groupJid, settings) {
|
|
402
|
+
if (!groupJid) {
|
|
403
|
+
throw new WasenderAPIError("Group JID is required.", 400);
|
|
404
|
+
}
|
|
405
|
+
if (Object.keys(settings).length === 0) {
|
|
406
|
+
throw new WasenderAPIError("Settings object cannot be empty.", 400);
|
|
407
|
+
}
|
|
408
|
+
return this.putInternal(
|
|
409
|
+
`/groups/${groupJid}/settings`,
|
|
410
|
+
settings
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
// ---------- Session Management Methods ----------
|
|
414
|
+
/**
|
|
415
|
+
* Retrieves a list of all WhatsApp sessions available to the authenticated user.
|
|
416
|
+
* @returns A promise that resolves to the list of WhatsApp sessions and rate limit information.
|
|
417
|
+
* @throws WasenderAPIError if the request fails.
|
|
418
|
+
*/
|
|
419
|
+
async getAllWhatsAppSessions() {
|
|
420
|
+
return this.getInternal("/whatsapp-sessions");
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Creates a new WhatsApp session.
|
|
424
|
+
* @param payload Details for the new session.
|
|
425
|
+
* @returns A promise that resolves to the created session information and rate limit information.
|
|
426
|
+
* @throws WasenderAPIError if the request fails.
|
|
427
|
+
*/
|
|
428
|
+
async createWhatsAppSession(payload) {
|
|
429
|
+
return this.postInternal("/whatsapp-sessions", payload);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Retrieves details for a specific WhatsApp session.
|
|
433
|
+
* @param sessionId ID of the WhatsApp session.
|
|
434
|
+
* @returns A promise that resolves to the session details and rate limit information.
|
|
435
|
+
* @throws WasenderAPIError if the request fails.
|
|
436
|
+
*/
|
|
437
|
+
async getWhatsAppSessionDetails(sessionId) {
|
|
438
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
439
|
+
return this.getInternal(`/whatsapp-sessions/${sessionId}`);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Updates details for a specific WhatsApp session.
|
|
443
|
+
* @param sessionId ID of the WhatsApp session.
|
|
444
|
+
* @param payload Fields to update.
|
|
445
|
+
* @returns A promise that resolves to the updated session details and rate limit information.
|
|
446
|
+
* @throws WasenderAPIError if the request fails.
|
|
447
|
+
*/
|
|
448
|
+
async updateWhatsAppSession(sessionId, payload) {
|
|
449
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
450
|
+
if (Object.keys(payload).length === 0) throw new WasenderAPIError("Update payload cannot be empty.", 400);
|
|
451
|
+
return this.putInternal(`/whatsapp-sessions/${sessionId}`, payload);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Deletes a specific WhatsApp session.
|
|
455
|
+
* @param sessionId ID of the WhatsApp session.
|
|
456
|
+
* @returns A promise that resolves to the deletion confirmation and rate limit information.
|
|
457
|
+
* @throws WasenderAPIError if the request fails.
|
|
458
|
+
*/
|
|
459
|
+
async deleteWhatsAppSession(sessionId) {
|
|
460
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
461
|
+
return this.deleteInternal(`/whatsapp-sessions/${sessionId}`);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Initiates the connection process for a WhatsApp session.
|
|
465
|
+
* @param sessionId ID of the WhatsApp session.
|
|
466
|
+
* @param qrAsImage Optional. If true, requests the QR code as an image in the response.
|
|
467
|
+
* @returns A promise that resolves to the connection status (e.g., QR code) and rate limit information.
|
|
468
|
+
* @throws WasenderAPIError if the request fails.
|
|
469
|
+
*/
|
|
470
|
+
async connectWhatsAppSession(sessionId, qrAsImage) {
|
|
471
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
472
|
+
const payload = qrAsImage !== void 0 ? { qr_as_image: qrAsImage } : null;
|
|
473
|
+
return this.postInternal(
|
|
474
|
+
`/whatsapp-sessions/${sessionId}/connect`,
|
|
475
|
+
payload
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Retrieves the QR code for connecting a WhatsApp session.
|
|
480
|
+
* @param sessionId ID of the WhatsApp session.
|
|
481
|
+
* @returns A promise that resolves to the QR code data and rate limit information.
|
|
482
|
+
* @throws WasenderAPIError if the request fails.
|
|
483
|
+
*/
|
|
484
|
+
async getWhatsAppSessionQRCode(sessionId) {
|
|
485
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
486
|
+
return this.getInternal(`/whatsapp-sessions/${sessionId}/qrcode`);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Disconnects an active WhatsApp session.
|
|
490
|
+
* @param sessionId ID of the WhatsApp session.
|
|
491
|
+
* @returns A promise that resolves to the disconnection status and rate limit information.
|
|
492
|
+
* @throws WasenderAPIError if the request fails.
|
|
493
|
+
*/
|
|
494
|
+
async disconnectWhatsAppSession(sessionId) {
|
|
495
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
496
|
+
return this.postInternal(`/whatsapp-sessions/${sessionId}/disconnect`, null);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Regenerates the API key for a specific WhatsApp session.
|
|
500
|
+
* Note: The response structure for this endpoint is unique.
|
|
501
|
+
* @param sessionId ID of the WhatsApp session.
|
|
502
|
+
* @returns A promise that resolves to the new API key and rate limit information.
|
|
503
|
+
* @throws WasenderAPIError if the request fails.
|
|
504
|
+
*/
|
|
505
|
+
async regenerateApiKey(sessionId) {
|
|
506
|
+
if (!sessionId) throw new WasenderAPIError("Session ID is required.", 400);
|
|
507
|
+
return this.request("POST", `/whatsapp-sessions/${sessionId}/regenerate-key`, {});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Retrieves the current status of the WhatsApp session associated with the API key.
|
|
511
|
+
* Note: The response structure for this endpoint is unique and does not include a session ID.
|
|
512
|
+
* @returns A promise that resolves to the session status. Rate limiting may or may not apply as per API docs.
|
|
513
|
+
* @throws WasenderAPIError if the request fails.
|
|
514
|
+
*/
|
|
515
|
+
async getSessionStatus() {
|
|
516
|
+
const result = await this.request("GET", "/status");
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
// ---------- Webhook Handling ----------
|
|
520
|
+
/**
|
|
521
|
+
* Verifies and parses an incoming Wasender webhook event.
|
|
522
|
+
* @param request An object that adapts your HTTP framework's request, providing methods to get headers and the raw body.
|
|
523
|
+
* @returns A promise that resolves to the parsed WasenderWebhookEvent.
|
|
524
|
+
* @throws WasenderAPIError if the webhook secret is not configured in the SDK, if the signature is invalid, or if the body cannot be parsed.
|
|
525
|
+
*/
|
|
526
|
+
async handleWebhookEvent(request) {
|
|
527
|
+
if (!this.configuredWebhookSecret) {
|
|
528
|
+
throw new WasenderAPIError("Webhook secret is not configured in the Wasender SDK instance. Cannot verify signature.");
|
|
529
|
+
}
|
|
530
|
+
const signature = request.getHeader(WEBHOOK_SIGNATURE_HEADER);
|
|
531
|
+
if (!verifyWasenderWebhookSignature(signature, this.configuredWebhookSecret)) {
|
|
532
|
+
throw new WasenderAPIError("Invalid webhook signature.", 401);
|
|
533
|
+
}
|
|
534
|
+
let rawBody;
|
|
535
|
+
try {
|
|
536
|
+
const bodyPromiseOrString = request.getRawBody();
|
|
537
|
+
rawBody = typeof bodyPromiseOrString === "string" ? bodyPromiseOrString : await bodyPromiseOrString;
|
|
538
|
+
} catch (err) {
|
|
539
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
540
|
+
throw new WasenderAPIError(`Failed to get raw body for webhook: ${message}`);
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const parsedEvent = JSON.parse(rawBody);
|
|
544
|
+
return parsedEvent;
|
|
545
|
+
} catch (err) {
|
|
546
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
547
|
+
throw new WasenderAPIError(`Failed to parse webhook JSON body: ${message}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var createWasender = (apiKey, personalAccessToken, baseUrl, fetchImplementation, retryOptions, webhookSecret) => new Wasender(apiKey, personalAccessToken, baseUrl, fetchImplementation, retryOptions, webhookSecret);
|
|
552
|
+
|
|
553
|
+
export { WEBHOOK_SIGNATURE_HEADER, Wasender, WasenderAPIError, createWasender, verifyWasenderWebhookSignature };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wabot-dev/framework",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Framework for IA Chat Bots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"socket.io-client": "^4.8.1",
|
|
66
66
|
"tslib": "^2.8.1",
|
|
67
67
|
"tsyringe": "^4.9.1",
|
|
68
|
-
"uuid": "^11.1.0"
|
|
68
|
+
"uuid": "^11.1.0",
|
|
69
|
+
"wasenderapi": "^0.1.5"
|
|
69
70
|
}
|
|
70
71
|
}
|