whatsapp-cloud 0.0.3 → 0.0.4
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/CHANGELOG.md +7 -0
- package/README.md +11 -0
- package/agent_docs/DESIGN.md +707 -0
- package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
- package/agent_docs/NAMING_DECISION.md +78 -0
- package/agent_docs/STRUCTURE.md +711 -0
- package/agent_docs/messages-namespace-design.md +357 -0
- package/package.json +5 -2
- package/src/client/HttpClient.ts +122 -0
- package/src/client/WhatsAppClient.ts +55 -0
- package/src/client/index.ts +2 -0
- package/src/errors.ts +58 -0
- package/src/index.ts +16 -1
- package/src/schemas/accounts/index.ts +1 -0
- package/src/schemas/accounts/phone-number.ts +20 -0
- package/src/schemas/business/account.ts +43 -0
- package/src/schemas/business/index.ts +2 -0
- package/src/schemas/client.ts +50 -0
- package/src/schemas/debug.ts +25 -0
- package/src/schemas/index.ts +5 -0
- package/src/schemas/messages/index.ts +2 -0
- package/src/schemas/messages/request.ts +82 -0
- package/src/schemas/messages/response.ts +19 -0
- package/src/services/accounts/AccountsClient.ts +42 -0
- package/src/services/accounts/AccountsService.ts +47 -0
- package/src/services/accounts/index.ts +2 -0
- package/src/services/accounts/methods/list-phone-numbers.ts +16 -0
- package/src/services/business/BusinessClient.ts +42 -0
- package/src/services/business/BusinessService.ts +47 -0
- package/src/services/business/index.ts +3 -0
- package/src/services/business/methods/list-accounts.ts +18 -0
- package/src/services/index.ts +2 -0
- package/src/services/messages/MessagesClient.ts +38 -0
- package/src/services/messages/MessagesService.ts +77 -0
- package/src/services/messages/index.ts +8 -0
- package/src/services/messages/methods/send-image.ts +33 -0
- package/src/services/messages/methods/send-location.ts +32 -0
- package/src/services/messages/methods/send-reaction.ts +33 -0
- package/src/services/messages/methods/send-text.ts +32 -0
- package/src/services/messages/utils/build-message-payload.ts +32 -0
- package/src/types/accounts/index.ts +1 -0
- package/src/types/accounts/phone-number.ts +9 -0
- package/src/types/business/account.ts +10 -0
- package/src/types/business/index.ts +2 -0
- package/src/types/client.ts +8 -0
- package/src/types/debug.ts +8 -0
- package/src/types/index.ts +5 -0
- package/src/types/messages/index.ts +2 -0
- package/src/types/messages/request.ts +27 -0
- package/src/types/messages/response.ts +7 -0
- package/src/utils/zod-error.ts +28 -0
- package/tsconfig.json +4 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper message for access token errors
|
|
5
|
+
*/
|
|
6
|
+
const ACCESS_TOKEN_HELP_MESSAGE =
|
|
7
|
+
"Get your access token from Meta for Developers: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Schema for access token with validation and helpful error messages
|
|
11
|
+
*/
|
|
12
|
+
const accessTokenSchema = z
|
|
13
|
+
.string({
|
|
14
|
+
message: `accessToken is required. ${ACCESS_TOKEN_HELP_MESSAGE}`,
|
|
15
|
+
})
|
|
16
|
+
.min(1, {
|
|
17
|
+
message: `accessToken cannot be empty. ${ACCESS_TOKEN_HELP_MESSAGE}`,
|
|
18
|
+
})
|
|
19
|
+
.trim()
|
|
20
|
+
.refine((val) => val.length > 0, {
|
|
21
|
+
message: `accessToken cannot be whitespace only. ${ACCESS_TOKEN_HELP_MESSAGE}`,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Client configuration schema
|
|
26
|
+
*/
|
|
27
|
+
export const clientConfigSchema = z.object({
|
|
28
|
+
accessToken: accessTokenSchema,
|
|
29
|
+
phoneNumberId: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
33
|
+
message: "phoneNumberId cannot be empty or whitespace only",
|
|
34
|
+
}),
|
|
35
|
+
businessAccountId: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
39
|
+
message: "businessAccountId cannot be empty or whitespace only",
|
|
40
|
+
}),
|
|
41
|
+
businessId: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
45
|
+
message: "businessId cannot be empty or whitespace only",
|
|
46
|
+
}),
|
|
47
|
+
apiVersion: z.string().default("v18.0"),
|
|
48
|
+
baseURL: z.string().url().default("https://graph.facebook.com"),
|
|
49
|
+
timeout: z.number().positive().optional(),
|
|
50
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for debug token response
|
|
5
|
+
* Matches Graph API debug_token endpoint response structure
|
|
6
|
+
*/
|
|
7
|
+
export const debugTokenResponseSchema = z.object({
|
|
8
|
+
data: z.object({
|
|
9
|
+
app_id: z.string().optional(),
|
|
10
|
+
type: z.string().optional(),
|
|
11
|
+
application: z.string().optional(),
|
|
12
|
+
data_access_expires_at: z.number().optional(),
|
|
13
|
+
expires_at: z.number().optional(),
|
|
14
|
+
is_valid: z.boolean().optional(),
|
|
15
|
+
issued_at: z.number().optional(),
|
|
16
|
+
metadata: z
|
|
17
|
+
.object({
|
|
18
|
+
auth_type: z.string().optional(),
|
|
19
|
+
sso: z.string().optional(),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
scopes: z.array(z.string()).optional(),
|
|
23
|
+
user_id: z.string().optional(),
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base schema for all message requests
|
|
5
|
+
* phoneNumberId is handled at the client level, not in the request object
|
|
6
|
+
*/
|
|
7
|
+
const baseMessageRequestSchema = z.object({
|
|
8
|
+
to: z.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format"),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Schema for image object in image messages
|
|
13
|
+
* Matches WhatsApp API structure
|
|
14
|
+
*/
|
|
15
|
+
const imageSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
id: z.string().optional(),
|
|
18
|
+
link: z.string().url().optional(),
|
|
19
|
+
caption: z.string().max(1024).optional(),
|
|
20
|
+
})
|
|
21
|
+
.refine((data) => data.link || data.id, "Either link or id must be provided");
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Schema for sending an image message
|
|
25
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
26
|
+
*/
|
|
27
|
+
export const sendImageRequestSchema = baseMessageRequestSchema.extend({
|
|
28
|
+
image: imageSchema,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Schema for text object in text messages
|
|
33
|
+
* Matches WhatsApp API structure
|
|
34
|
+
*/
|
|
35
|
+
const textSchema = z.object({
|
|
36
|
+
body: z.string().min(1).max(4096),
|
|
37
|
+
preview_url: z.boolean().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Schema for sending a text message
|
|
42
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
43
|
+
*/
|
|
44
|
+
export const sendTextRequestSchema = baseMessageRequestSchema.extend({
|
|
45
|
+
text: textSchema,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Schema for location object in location messages
|
|
50
|
+
* Matches WhatsApp API structure
|
|
51
|
+
*/
|
|
52
|
+
const locationSchema = z.object({
|
|
53
|
+
longitude: z.number().min(-180).max(180),
|
|
54
|
+
latitude: z.number().min(-90).max(90),
|
|
55
|
+
name: z.string().optional(),
|
|
56
|
+
address: z.string().optional(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Schema for sending a location message
|
|
61
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
62
|
+
*/
|
|
63
|
+
export const sendLocationRequestSchema = baseMessageRequestSchema.extend({
|
|
64
|
+
location: locationSchema,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Schema for reaction object in reaction messages
|
|
69
|
+
* Matches WhatsApp API structure
|
|
70
|
+
*/
|
|
71
|
+
const reactionSchema = z.object({
|
|
72
|
+
message_id: z.string().min(1),
|
|
73
|
+
emoji: z.string().min(1).max(1), // Single emoji character
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Schema for sending a reaction message
|
|
78
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
79
|
+
*/
|
|
80
|
+
export const sendReactionRequestSchema = baseMessageRequestSchema.extend({
|
|
81
|
+
reaction: reactionSchema,
|
|
82
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for message response
|
|
5
|
+
*/
|
|
6
|
+
export const messageResponseSchema = z.object({
|
|
7
|
+
messaging_product: z.literal("whatsapp"),
|
|
8
|
+
contacts: z.array(
|
|
9
|
+
z.object({
|
|
10
|
+
input: z.string(),
|
|
11
|
+
wa_id: z.string(),
|
|
12
|
+
})
|
|
13
|
+
),
|
|
14
|
+
messages: z.array(
|
|
15
|
+
z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
})
|
|
18
|
+
),
|
|
19
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Accounts client - wraps HttpClient with WABA ID (WhatsApp Business Account ID) as base endpoint
|
|
5
|
+
*
|
|
6
|
+
* This client automatically prepends `/${wabaId}` to all request paths,
|
|
7
|
+
* so methods can use relative paths like `/phone_numbers` instead of `/${wabaId}/phone_numbers`.
|
|
8
|
+
*
|
|
9
|
+
* Note: The wabaId is the WhatsApp Business Account ID (not the Business Portfolio ID).
|
|
10
|
+
* This is used in endpoints like GET /<WABA_ID>/phone_numbers.
|
|
11
|
+
*
|
|
12
|
+
* This treats wabaId as a "client" for the accounts namespace - different
|
|
13
|
+
* wabaIds represent different WhatsApp Business Account endpoints.
|
|
14
|
+
*/
|
|
15
|
+
export class AccountsClient {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly httpClient: HttpClient,
|
|
18
|
+
private readonly wabaId: string
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make a GET request with WABA ID prefix
|
|
23
|
+
*/
|
|
24
|
+
async get<T>(path: string): Promise<T> {
|
|
25
|
+
return this.httpClient.get<T>(`/${this.wabaId}${path}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Make a POST request with WABA ID prefix
|
|
30
|
+
*/
|
|
31
|
+
async post<T>(path: string, body: unknown): Promise<T> {
|
|
32
|
+
return this.httpClient.post<T>(`/${this.wabaId}${path}`, body);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make a PATCH request with WABA ID prefix
|
|
37
|
+
*/
|
|
38
|
+
async patch<T>(path: string, body: unknown): Promise<T> {
|
|
39
|
+
return this.httpClient.patch<T>(`/${this.wabaId}${path}`, body);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
import { AccountsClient } from "./AccountsClient";
|
|
3
|
+
import { listPhoneNumbers } from "./methods/list-phone-numbers";
|
|
4
|
+
import { WhatsAppValidationError } from "../../errors";
|
|
5
|
+
import type { PhoneNumberListResponse } from "../../types/accounts/phone-number";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Accounts service for managing WhatsApp Business Accounts
|
|
9
|
+
*
|
|
10
|
+
* The service validates that businessAccountId (WABA ID - WhatsApp Business Account ID) is set
|
|
11
|
+
* at the client level and creates an AccountsClient instance.
|
|
12
|
+
*
|
|
13
|
+
* Note: businessAccountId in the client config represents the WABA ID, not the Business Portfolio ID.
|
|
14
|
+
* The WABA ID is used in endpoints like GET /<WABA_ID>/phone_numbers.
|
|
15
|
+
*
|
|
16
|
+
* AccountsClient treats wabaId as a "client" for the accounts namespace - different
|
|
17
|
+
* wabaIds represent different WhatsApp Business Account endpoints.
|
|
18
|
+
*/
|
|
19
|
+
export class AccountsService {
|
|
20
|
+
private readonly accountsClient: AccountsClient;
|
|
21
|
+
|
|
22
|
+
constructor(httpClient: HttpClient) {
|
|
23
|
+
// Validate that businessAccountId (WABA ID) is set at client level
|
|
24
|
+
// This is the WhatsApp Business Account ID, not the Business Portfolio ID
|
|
25
|
+
if (!httpClient.businessAccountId) {
|
|
26
|
+
throw new WhatsAppValidationError(
|
|
27
|
+
"businessAccountId (WABA ID - WhatsApp Business Account ID) is required for AccountsService. Provide it in WhatsAppClient config.",
|
|
28
|
+
"businessAccountId"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create accounts client with WABA ID baked in
|
|
33
|
+
this.accountsClient = new AccountsClient(
|
|
34
|
+
httpClient,
|
|
35
|
+
httpClient.businessAccountId
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* List phone numbers for a WhatsApp Business Account
|
|
41
|
+
*
|
|
42
|
+
* @returns List of phone numbers associated with the WABA
|
|
43
|
+
*/
|
|
44
|
+
async listPhoneNumbers(): Promise<PhoneNumberListResponse> {
|
|
45
|
+
return listPhoneNumbers(this.accountsClient);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AccountsClient } from "../AccountsClient";
|
|
2
|
+
import type { PhoneNumberListResponse } from "../../../types/accounts/phone-number";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List phone numbers for a WhatsApp Business Account
|
|
6
|
+
*
|
|
7
|
+
* @param accountsClient - Accounts client with WABA ID baked in
|
|
8
|
+
* @returns List of phone numbers associated with the WABA
|
|
9
|
+
*/
|
|
10
|
+
export async function listPhoneNumbers(
|
|
11
|
+
accountsClient: AccountsClient
|
|
12
|
+
): Promise<PhoneNumberListResponse> {
|
|
13
|
+
// Make API request - accountsClient handles the WABA ID prefix automatically
|
|
14
|
+
return accountsClient.get<PhoneNumberListResponse>("/phone_numbers");
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Business client - wraps HttpClient with Business Portfolio ID as base endpoint
|
|
5
|
+
*
|
|
6
|
+
* This client automatically prepends `/${businessId}` to all request paths,
|
|
7
|
+
* so methods can use relative paths like `/whatsapp_business_accounts` instead of `/${businessId}/whatsapp_business_accounts`.
|
|
8
|
+
*
|
|
9
|
+
* Note: The businessId is the Business Portfolio ID (not the WABA ID).
|
|
10
|
+
* This is used in endpoints like GET /<Business-ID>/whatsapp_business_accounts.
|
|
11
|
+
*
|
|
12
|
+
* This treats businessId as a "client" for the business namespace - different
|
|
13
|
+
* businessIds represent different Business Portfolio endpoints.
|
|
14
|
+
*/
|
|
15
|
+
export class BusinessClient {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly httpClient: HttpClient,
|
|
18
|
+
private readonly businessId: string
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make a GET request with Business Portfolio ID prefix
|
|
23
|
+
*/
|
|
24
|
+
async get<T>(path: string): Promise<T> {
|
|
25
|
+
return this.httpClient.get<T>(`/${this.businessId}${path}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Make a POST request with Business Portfolio ID prefix
|
|
30
|
+
*/
|
|
31
|
+
async post<T>(path: string, body: unknown): Promise<T> {
|
|
32
|
+
return this.httpClient.post<T>(`/${this.businessId}${path}`, body);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make a PATCH request with Business Portfolio ID prefix
|
|
37
|
+
*/
|
|
38
|
+
async patch<T>(path: string, body: unknown): Promise<T> {
|
|
39
|
+
return this.httpClient.patch<T>(`/${this.businessId}${path}`, body);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
import { BusinessClient } from "./BusinessClient";
|
|
3
|
+
import { listAccounts } from "./methods/list-accounts";
|
|
4
|
+
import { WhatsAppValidationError } from "../../errors";
|
|
5
|
+
import type { BusinessAccountsListResponse } from "../../types/business/account";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Business service for managing Business Portfolios
|
|
9
|
+
*
|
|
10
|
+
* The service validates that businessId (Business Portfolio ID) is set at the client level
|
|
11
|
+
* and creates a BusinessClient instance.
|
|
12
|
+
*
|
|
13
|
+
* Note: businessId in the client config represents the Business Portfolio ID.
|
|
14
|
+
* The Business Portfolio ID is used in endpoints like GET /<Business-ID>/whatsapp_business_accounts.
|
|
15
|
+
*
|
|
16
|
+
* BusinessClient treats businessId as a "client" for the business namespace - different
|
|
17
|
+
* businessIds represent different Business Portfolio endpoints.
|
|
18
|
+
*/
|
|
19
|
+
export class BusinessService {
|
|
20
|
+
private readonly businessClient: BusinessClient;
|
|
21
|
+
|
|
22
|
+
constructor(httpClient: HttpClient) {
|
|
23
|
+
// Validate that businessId (Business Portfolio ID) is set at client level
|
|
24
|
+
if (!httpClient.businessId) {
|
|
25
|
+
throw new WhatsAppValidationError(
|
|
26
|
+
"businessId (Business Portfolio ID) is required for BusinessService. Provide it in WhatsAppClient config.",
|
|
27
|
+
"businessId"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create business client with Business Portfolio ID baked in
|
|
32
|
+
this.businessClient = new BusinessClient(
|
|
33
|
+
httpClient,
|
|
34
|
+
httpClient.businessId
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* List WhatsApp Business Accounts (WABAs) for a Business Portfolio
|
|
40
|
+
*
|
|
41
|
+
* @returns List of WABAs associated with the Business Portfolio
|
|
42
|
+
*/
|
|
43
|
+
async listAccounts(): Promise<BusinessAccountsListResponse> {
|
|
44
|
+
return listAccounts(this.businessClient);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BusinessClient } from "../BusinessClient";
|
|
2
|
+
import type { BusinessAccountsListResponse } from "../../../types/business/account";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List WhatsApp Business Accounts (WABAs) for a Business Portfolio
|
|
6
|
+
*
|
|
7
|
+
* @param businessClient - Business client with Business Portfolio ID baked in
|
|
8
|
+
* @returns List of WABAs associated with the Business Portfolio
|
|
9
|
+
*/
|
|
10
|
+
export async function listAccounts(
|
|
11
|
+
businessClient: BusinessClient
|
|
12
|
+
): Promise<BusinessAccountsListResponse> {
|
|
13
|
+
// Make API request - businessClient handles the Business Portfolio ID prefix automatically
|
|
14
|
+
return businessClient.get<BusinessAccountsListResponse>(
|
|
15
|
+
"/whatsapp_business_accounts"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Messages client - wraps HttpClient with phone number ID as base endpoint
|
|
5
|
+
*
|
|
6
|
+
* This client automatically prepends `/${phoneNumberId}` to all request paths,
|
|
7
|
+
* so methods can use relative paths like `/messages` instead of `/${phoneNumberId}/messages`.
|
|
8
|
+
*
|
|
9
|
+
* This treats phoneNumberId as a "client" for the messaging namespace - different
|
|
10
|
+
* phoneNumberIds represent different messaging endpoints.
|
|
11
|
+
*/
|
|
12
|
+
export class MessagesClient {
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly httpClient: HttpClient,
|
|
15
|
+
private readonly phoneNumberId: string
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Make a POST request with phone number ID prefix
|
|
20
|
+
*/
|
|
21
|
+
async post<T>(path: string, body: unknown): Promise<T> {
|
|
22
|
+
return this.httpClient.post<T>(`/${this.phoneNumberId}${path}`, body);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Make a GET request with phone number ID prefix
|
|
27
|
+
*/
|
|
28
|
+
async get<T>(path: string): Promise<T> {
|
|
29
|
+
return this.httpClient.get<T>(`/${this.phoneNumberId}${path}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Make a PATCH request with phone number ID prefix
|
|
34
|
+
*/
|
|
35
|
+
async patch<T>(path: string, body: unknown): Promise<T> {
|
|
36
|
+
return this.httpClient.patch<T>(`/${this.phoneNumberId}${path}`, body);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
import { sendText } from "./methods/send-text";
|
|
3
|
+
import { sendImage } from "./methods/send-image";
|
|
4
|
+
import { sendLocation } from "./methods/send-location";
|
|
5
|
+
import { sendReaction } from "./methods/send-reaction";
|
|
6
|
+
import { MessagesClient } from "./MessagesClient";
|
|
7
|
+
import { WhatsAppValidationError } from "../../errors";
|
|
8
|
+
import type {
|
|
9
|
+
SendTextRequest,
|
|
10
|
+
SendImageRequest,
|
|
11
|
+
SendLocationRequest,
|
|
12
|
+
SendReactionRequest,
|
|
13
|
+
} from "../../types/messages/request";
|
|
14
|
+
import type { MessageResponse } from "../../types/messages/response";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Messages service for sending WhatsApp messages
|
|
18
|
+
*
|
|
19
|
+
* The service validates that phoneNumberId is set at the client level and creates
|
|
20
|
+
* a MessagesClient instance. MessagesClient treats phoneNumberId as a "client" for
|
|
21
|
+
* the messaging namespace - different phoneNumberIds represent different endpoints.
|
|
22
|
+
*/
|
|
23
|
+
export class MessagesService {
|
|
24
|
+
private readonly messagesClient: MessagesClient;
|
|
25
|
+
|
|
26
|
+
constructor(httpClient: HttpClient) {
|
|
27
|
+
// Validate that phoneNumberId is set at client level
|
|
28
|
+
if (!httpClient.phoneNumberId) {
|
|
29
|
+
throw new WhatsAppValidationError(
|
|
30
|
+
"phoneNumberId is required for MessagesService. Provide it in WhatsAppClient config.",
|
|
31
|
+
"phoneNumberId"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create messages client with phone number ID baked in
|
|
36
|
+
this.messagesClient = new MessagesClient(
|
|
37
|
+
httpClient,
|
|
38
|
+
httpClient.phoneNumberId
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Send a text message
|
|
44
|
+
*
|
|
45
|
+
* @param request - Text message request (to, text)
|
|
46
|
+
*/
|
|
47
|
+
async sendText(request: SendTextRequest): Promise<MessageResponse> {
|
|
48
|
+
return sendText(this.messagesClient, request);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Send an image message
|
|
53
|
+
*
|
|
54
|
+
* @param request - Image message request (to, image)
|
|
55
|
+
*/
|
|
56
|
+
async sendImage(request: SendImageRequest): Promise<MessageResponse> {
|
|
57
|
+
return sendImage(this.messagesClient, request);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Send a location message
|
|
62
|
+
*
|
|
63
|
+
* @param request - Location message request (to, location)
|
|
64
|
+
*/
|
|
65
|
+
async sendLocation(request: SendLocationRequest): Promise<MessageResponse> {
|
|
66
|
+
return sendLocation(this.messagesClient, request);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Send a reaction message
|
|
71
|
+
*
|
|
72
|
+
* @param request - Reaction message request (to, reaction)
|
|
73
|
+
*/
|
|
74
|
+
async sendReaction(request: SendReactionRequest): Promise<MessageResponse> {
|
|
75
|
+
return sendReaction(this.messagesClient, request);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { MessagesService } from "./MessagesService";
|
|
2
|
+
export type {
|
|
3
|
+
SendTextRequest,
|
|
4
|
+
SendImageRequest,
|
|
5
|
+
SendLocationRequest,
|
|
6
|
+
SendReactionRequest,
|
|
7
|
+
} from "../../types/messages/request";
|
|
8
|
+
export type { MessageResponse } from "../../types/messages/response";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MessagesClient } from "../MessagesClient";
|
|
2
|
+
import { sendImageRequestSchema } from "../../../schemas/messages/request";
|
|
3
|
+
import type { SendImageRequest } from "../../../types/messages/request";
|
|
4
|
+
import type { MessageResponse } from "../../../types/messages/response";
|
|
5
|
+
import { buildMessagePayload } from "../utils/build-message-payload";
|
|
6
|
+
import { transformZodError } from "../../../utils/zod-error";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send an image message
|
|
10
|
+
*
|
|
11
|
+
* @param messagesClient - Messages client with phone number ID baked in
|
|
12
|
+
* @param request - Image message request (to, image)
|
|
13
|
+
*/
|
|
14
|
+
export async function sendImage(
|
|
15
|
+
messagesClient: MessagesClient,
|
|
16
|
+
request: SendImageRequest
|
|
17
|
+
): Promise<MessageResponse> {
|
|
18
|
+
// Validate request with schema - throws WhatsAppValidationError if invalid
|
|
19
|
+
const result = sendImageRequestSchema.safeParse(request);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
throw transformZodError(result.error);
|
|
22
|
+
}
|
|
23
|
+
const data = result.data;
|
|
24
|
+
|
|
25
|
+
// Build message payload using common structure
|
|
26
|
+
// The request.image already matches the API structure, so we can pass it directly
|
|
27
|
+
const payload = buildMessagePayload(data.to, "image", {
|
|
28
|
+
image: data.image,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Make API request - messagesClient handles the phoneNumberId prefix automatically
|
|
32
|
+
return messagesClient.post<MessageResponse>("/messages", payload);
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MessagesClient } from "../MessagesClient";
|
|
2
|
+
import { sendLocationRequestSchema } from "../../../schemas/messages/request";
|
|
3
|
+
import type { SendLocationRequest } from "../../../types/messages/request";
|
|
4
|
+
import type { MessageResponse } from "../../../types/messages/response";
|
|
5
|
+
import { buildMessagePayload } from "../utils/build-message-payload";
|
|
6
|
+
import { transformZodError } from "../../../utils/zod-error";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send a location message
|
|
10
|
+
*
|
|
11
|
+
* @param messagesClient - Messages client with phone number ID baked in
|
|
12
|
+
* @param request - Location message request (to, location)
|
|
13
|
+
*/
|
|
14
|
+
export async function sendLocation(
|
|
15
|
+
messagesClient: MessagesClient,
|
|
16
|
+
request: SendLocationRequest
|
|
17
|
+
): Promise<MessageResponse> {
|
|
18
|
+
// Validate request with schema - throws WhatsAppValidationError if invalid
|
|
19
|
+
const result = sendLocationRequestSchema.safeParse(request);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
throw transformZodError(result.error);
|
|
22
|
+
}
|
|
23
|
+
const data = result.data;
|
|
24
|
+
|
|
25
|
+
// Build message payload using common structure
|
|
26
|
+
const payload = buildMessagePayload(data.to, "location", {
|
|
27
|
+
location: data.location,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Make API request - messagesClient handles the phoneNumberId prefix automatically
|
|
31
|
+
return messagesClient.post<MessageResponse>("/messages", payload);
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MessagesClient } from "../MessagesClient";
|
|
2
|
+
import { sendReactionRequestSchema } from "../../../schemas/messages/request";
|
|
3
|
+
import type { SendReactionRequest } from "../../../types/messages/request";
|
|
4
|
+
import type { MessageResponse } from "../../../types/messages/response";
|
|
5
|
+
import { buildMessagePayload } from "../utils/build-message-payload";
|
|
6
|
+
import { transformZodError } from "../../../utils/zod-error";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send a reaction message
|
|
10
|
+
*
|
|
11
|
+
* @param messagesClient - Messages client with phone number ID baked in
|
|
12
|
+
* @param request - Reaction message request (to, reaction)
|
|
13
|
+
*/
|
|
14
|
+
export async function sendReaction(
|
|
15
|
+
messagesClient: MessagesClient,
|
|
16
|
+
request: SendReactionRequest
|
|
17
|
+
): Promise<MessageResponse> {
|
|
18
|
+
// Validate request with schema - throws WhatsAppValidationError if invalid
|
|
19
|
+
const result = sendReactionRequestSchema.safeParse(request);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
throw transformZodError(result.error);
|
|
22
|
+
}
|
|
23
|
+
const data = result.data;
|
|
24
|
+
|
|
25
|
+
// Build message payload using common structure
|
|
26
|
+
const payload = buildMessagePayload(data.to, "reaction", {
|
|
27
|
+
reaction: data.reaction,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Make API request - messagesClient handles the phoneNumberId prefix automatically
|
|
31
|
+
return messagesClient.post<MessageResponse>("/messages", payload);
|
|
32
|
+
}
|
|
33
|
+
|