@operor/provider-wati 0.1.0

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,46 @@
1
+ import EventEmitter from "eventemitter3";
2
+ import { MessageProvider, OutgoingMessage } from "@operor/core";
3
+
4
+ //#region src/types.d.ts
5
+ interface WatiProviderConfig {
6
+ /** WATI API bearer token */
7
+ apiToken: string;
8
+ /** WATI tenant ID (appears in the API base URL) */
9
+ tenantId: string;
10
+ /** Port for the webhook HTTP server (default: 3001) */
11
+ webhookPort?: number;
12
+ /** Path the webhook listens on (default: /webhook/wati) */
13
+ webhookPath?: string;
14
+ /** Override the WATI API base URL (default: https://live-mt-server.wati.io/{tenantId}/api) */
15
+ apiBaseUrl?: string;
16
+ }
17
+ interface WatiTemplateParams {
18
+ templateName: string;
19
+ parameters: Array<{
20
+ name: string;
21
+ value: string;
22
+ }>;
23
+ }
24
+ //#endregion
25
+ //#region src/WatiProvider.d.ts
26
+ declare class WatiProvider extends EventEmitter implements MessageProvider {
27
+ readonly name = "wati";
28
+ private config;
29
+ private server;
30
+ private connected;
31
+ private baseUrl;
32
+ private webhookPort;
33
+ private webhookPath;
34
+ constructor(config: WatiProviderConfig);
35
+ connect(): Promise<void>;
36
+ disconnect(): Promise<void>;
37
+ sendMessage(to: string, message: OutgoingMessage): Promise<void>;
38
+ sendTemplateMessage(to: string, template: WatiTemplateParams): Promise<void>;
39
+ isActive(): boolean;
40
+ private handleVerification;
41
+ private handleWebhookRequest;
42
+ private apiRequest;
43
+ }
44
+ //#endregion
45
+ export { WatiProvider, type WatiProviderConfig, type WatiTemplateParams };
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/WatiProvider.ts"],"mappings":";;;;UAAiB,kBAAA;;EAEf,QAAA;;EAEA,QAAA;EAJiC;EAMjC,WAAA;EANiC;EAQjC,WAAA;EAJA;EAMA,UAAA;AAAA;AAAA,UAce,kBAAA;EACf,YAAA;EACA,UAAA,EAAY,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;AAAA;;;cCrBvB,YAAA,SAAqB,YAAA,YAAwB,eAAA;EAAA,SACxC,IAAA;EAAA,QACR,MAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,OAAA;EAAA,QACA,WAAA;EAAA,QACA,WAAA;cAEI,MAAA,EAAQ,kBAAA;EAQd,OAAA,CAAA,GAAW,OAAA;EA2BX,UAAA,CAAA,GAAc,OAAA;EAWd,WAAA,CAAY,EAAA,UAAY,OAAA,EAAS,eAAA,GAAkB,OAAA;EAanD,mBAAA,CAAoB,EAAA,UAAY,QAAA,EAAU,kBAAA,GAAqB,OAAA;EAgBrE,QAAA,CAAA;EAAA,QAMQ,kBAAA;EAAA,QAUA,oBAAA;EAAA,QAoCM,UAAA;AAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,124 @@
1
+ import EventEmitter from "eventemitter3";
2
+ import * as http from "node:http";
3
+
4
+ //#region src/WatiProvider.ts
5
+ var WatiProvider = class extends EventEmitter {
6
+ name = "wati";
7
+ config;
8
+ server = null;
9
+ connected = false;
10
+ baseUrl;
11
+ webhookPort;
12
+ webhookPath;
13
+ constructor(config) {
14
+ super();
15
+ this.config = config;
16
+ this.baseUrl = config.apiBaseUrl || `https://live-mt-server.wati.io/${config.tenantId}/api`;
17
+ this.webhookPort = config.webhookPort || 3001;
18
+ this.webhookPath = config.webhookPath || "/webhook/wati";
19
+ }
20
+ async connect() {
21
+ this.server = http.createServer((req, res) => {
22
+ if (req.url?.startsWith(this.webhookPath)) if (req.method === "GET") this.handleVerification(req, res);
23
+ else if (req.method === "POST") this.handleWebhookRequest(req, res);
24
+ else res.writeHead(405).end();
25
+ else res.writeHead(404).end();
26
+ });
27
+ await new Promise((resolve) => {
28
+ this.server.listen(this.webhookPort, () => {
29
+ this.connected = true;
30
+ console.log(`āœ… WATI webhook server listening on port ${this.webhookPort}`);
31
+ console.log(` Webhook path: ${this.webhookPath}`);
32
+ resolve();
33
+ });
34
+ });
35
+ this.emit("ready");
36
+ }
37
+ async disconnect() {
38
+ if (this.server) {
39
+ await new Promise((resolve, reject) => {
40
+ this.server.close((err) => err ? reject(err) : resolve());
41
+ });
42
+ this.server = null;
43
+ }
44
+ this.connected = false;
45
+ console.log("WATI provider disconnected");
46
+ }
47
+ async sendMessage(to, message) {
48
+ if (!this.connected) throw new Error("WATI provider not connected");
49
+ await this.apiRequest(`/v1/sendSessionMessage/${to}`, {
50
+ method: "POST",
51
+ body: JSON.stringify({ messageText: message.text })
52
+ });
53
+ console.log(`šŸ“¤ [WATI] Sent reply to ${to}`);
54
+ }
55
+ async sendTemplateMessage(to, template) {
56
+ if (!this.connected) throw new Error("WATI provider not connected");
57
+ await this.apiRequest(`/v2/sendTemplateMessage?whatsappNumber=${encodeURIComponent(to)}`, {
58
+ method: "POST",
59
+ body: JSON.stringify({
60
+ template_name: template.templateName,
61
+ parameters: template.parameters
62
+ })
63
+ });
64
+ console.log(`šŸ“¤ [WATI] Sent template "${template.templateName}" to ${to}`);
65
+ }
66
+ isActive() {
67
+ return this.connected;
68
+ }
69
+ handleVerification(req, res) {
70
+ const challenge = new URL(req.url, `http://localhost:${this.webhookPort}`).searchParams.get("hub.challenge");
71
+ if (challenge) res.writeHead(200, { "Content-Type": "text/plain" }).end(challenge);
72
+ else res.writeHead(200).end("ok");
73
+ }
74
+ handleWebhookRequest(req, res) {
75
+ let body = "";
76
+ req.on("data", (chunk) => {
77
+ body += chunk.toString();
78
+ });
79
+ req.on("end", () => {
80
+ res.writeHead(200).end("ok");
81
+ try {
82
+ const payload = JSON.parse(body);
83
+ if (payload.eventType !== "messageReceived" || payload.fromMe) return;
84
+ const msg = {
85
+ id: payload.whatsappMessageId,
86
+ from: payload.waId,
87
+ text: payload.text,
88
+ timestamp: new Date(payload.timestamp).getTime(),
89
+ channel: "whatsapp",
90
+ provider: this.name,
91
+ metadata: {
92
+ senderName: payload.senderName,
93
+ messageType: payload.type
94
+ }
95
+ };
96
+ console.log(`\nšŸ“„ [WATI] New message from ${payload.senderName || payload.waId}`);
97
+ console.log(` Message: "${msg.text}"`);
98
+ this.emit("message", msg);
99
+ } catch (err) {
100
+ console.error("āŒ [WATI] Failed to parse webhook payload:", err);
101
+ }
102
+ });
103
+ }
104
+ async apiRequest(path, init) {
105
+ const url = `${this.baseUrl}${path}`;
106
+ const res = await fetch(url, {
107
+ ...init,
108
+ headers: {
109
+ "Authorization": `Bearer ${this.config.apiToken}`,
110
+ "Content-Type": "application/json",
111
+ ...init.headers
112
+ }
113
+ });
114
+ if (!res.ok) {
115
+ const text = await res.text().catch(() => "");
116
+ throw new Error(`WATI API error ${res.status}: ${text}`);
117
+ }
118
+ return res;
119
+ }
120
+ };
121
+
122
+ //#endregion
123
+ export { WatiProvider };
124
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/WatiProvider.ts"],"sourcesContent":["import EventEmitter from 'eventemitter3';\nimport * as http from 'node:http';\nimport type { MessageProvider, IncomingMessage, OutgoingMessage } from '@operor/core';\nimport type { WatiProviderConfig, WatiWebhookPayload, WatiTemplateParams } from './types.js';\n\nexport class WatiProvider extends EventEmitter implements MessageProvider {\n public readonly name = 'wati';\n private config: WatiProviderConfig;\n private server: http.Server | null = null;\n private connected = false;\n private baseUrl: string;\n private webhookPort: number;\n private webhookPath: string;\n\n constructor(config: WatiProviderConfig) {\n super();\n this.config = config;\n this.baseUrl = config.apiBaseUrl || `https://live-mt-server.wati.io/${config.tenantId}/api`;\n this.webhookPort = config.webhookPort || 3001;\n this.webhookPath = config.webhookPath || '/webhook/wati';\n }\n\n async connect(): Promise<void> {\n this.server = http.createServer((req, res) => {\n if (req.url?.startsWith(this.webhookPath)) {\n if (req.method === 'GET') {\n this.handleVerification(req, res);\n } else if (req.method === 'POST') {\n this.handleWebhookRequest(req, res);\n } else {\n res.writeHead(405).end();\n }\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise<void>((resolve) => {\n this.server!.listen(this.webhookPort, () => {\n this.connected = true;\n console.log(`āœ… WATI webhook server listening on port ${this.webhookPort}`);\n console.log(` Webhook path: ${this.webhookPath}`);\n resolve();\n });\n });\n\n this.emit('ready');\n }\n\n async disconnect(): Promise<void> {\n if (this.server) {\n await new Promise<void>((resolve, reject) => {\n this.server!.close((err) => (err ? reject(err) : resolve()));\n });\n this.server = null;\n }\n this.connected = false;\n console.log('WATI provider disconnected');\n }\n\n async sendMessage(to: string, message: OutgoingMessage): Promise<void> {\n if (!this.connected) {\n throw new Error('WATI provider not connected');\n }\n\n await this.apiRequest(`/v1/sendSessionMessage/${to}`, {\n method: 'POST',\n body: JSON.stringify({ messageText: message.text }),\n });\n\n console.log(`šŸ“¤ [WATI] Sent reply to ${to}`);\n }\n\n async sendTemplateMessage(to: string, template: WatiTemplateParams): Promise<void> {\n if (!this.connected) {\n throw new Error('WATI provider not connected');\n }\n\n await this.apiRequest(`/v2/sendTemplateMessage?whatsappNumber=${encodeURIComponent(to)}`, {\n method: 'POST',\n body: JSON.stringify({\n template_name: template.templateName,\n parameters: template.parameters,\n }),\n });\n\n console.log(`šŸ“¤ [WATI] Sent template \"${template.templateName}\" to ${to}`);\n }\n\n isActive(): boolean {\n return this.connected;\n }\n\n // --- Private helpers ---\n\n private handleVerification(req: http.IncomingMessage, res: http.ServerResponse): void {\n const url = new URL(req.url!, `http://localhost:${this.webhookPort}`);\n const challenge = url.searchParams.get('hub.challenge');\n if (challenge) {\n res.writeHead(200, { 'Content-Type': 'text/plain' }).end(challenge);\n } else {\n res.writeHead(200).end('ok');\n }\n }\n\n private handleWebhookRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n let body = '';\n req.on('data', (chunk: Buffer) => { body += chunk.toString(); });\n req.on('end', () => {\n res.writeHead(200).end('ok');\n\n try {\n const payload: WatiWebhookPayload = JSON.parse(body);\n\n if (payload.eventType !== 'messageReceived' || payload.fromMe) {\n return;\n }\n\n const msg: IncomingMessage = {\n id: payload.whatsappMessageId,\n from: payload.waId,\n text: payload.text,\n timestamp: new Date(payload.timestamp).getTime(),\n channel: 'whatsapp',\n provider: this.name,\n metadata: {\n senderName: payload.senderName,\n messageType: payload.type,\n },\n };\n\n console.log(`\\nšŸ“„ [WATI] New message from ${payload.senderName || payload.waId}`);\n console.log(` Message: \"${msg.text}\"`);\n\n this.emit('message', msg);\n } catch (err) {\n console.error('āŒ [WATI] Failed to parse webhook payload:', err);\n }\n });\n }\n\n private async apiRequest(path: string, init: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n const res = await fetch(url, {\n ...init,\n headers: {\n 'Authorization': `Bearer ${this.config.apiToken}`,\n 'Content-Type': 'application/json',\n ...init.headers,\n },\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`WATI API error ${res.status}: ${text}`);\n }\n\n return res;\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@operor/provider-wati",
3
+ "version": "0.1.0",
4
+ "description": "WATI WhatsApp Business API provider for Agent OS",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "dependencies": {
9
+ "eventemitter3": "^5.0.1",
10
+ "@operor/core": "0.1.0"
11
+ },
12
+ "devDependencies": {
13
+ "tsdown": "^0.20.3",
14
+ "typescript": "^5.3.3"
15
+ },
16
+ "scripts": {
17
+ "build": "tsdown",
18
+ "dev": "tsdown --watch"
19
+ }
20
+ }
@@ -0,0 +1,160 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ import * as http from 'node:http';
3
+ import type { MessageProvider, IncomingMessage, OutgoingMessage } from '@operor/core';
4
+ import type { WatiProviderConfig, WatiWebhookPayload, WatiTemplateParams } from './types.js';
5
+
6
+ export class WatiProvider extends EventEmitter implements MessageProvider {
7
+ public readonly name = 'wati';
8
+ private config: WatiProviderConfig;
9
+ private server: http.Server | null = null;
10
+ private connected = false;
11
+ private baseUrl: string;
12
+ private webhookPort: number;
13
+ private webhookPath: string;
14
+
15
+ constructor(config: WatiProviderConfig) {
16
+ super();
17
+ this.config = config;
18
+ this.baseUrl = config.apiBaseUrl || `https://live-mt-server.wati.io/${config.tenantId}/api`;
19
+ this.webhookPort = config.webhookPort || 3001;
20
+ this.webhookPath = config.webhookPath || '/webhook/wati';
21
+ }
22
+
23
+ async connect(): Promise<void> {
24
+ this.server = http.createServer((req, res) => {
25
+ if (req.url?.startsWith(this.webhookPath)) {
26
+ if (req.method === 'GET') {
27
+ this.handleVerification(req, res);
28
+ } else if (req.method === 'POST') {
29
+ this.handleWebhookRequest(req, res);
30
+ } else {
31
+ res.writeHead(405).end();
32
+ }
33
+ } else {
34
+ res.writeHead(404).end();
35
+ }
36
+ });
37
+
38
+ await new Promise<void>((resolve) => {
39
+ this.server!.listen(this.webhookPort, () => {
40
+ this.connected = true;
41
+ console.log(`āœ… WATI webhook server listening on port ${this.webhookPort}`);
42
+ console.log(` Webhook path: ${this.webhookPath}`);
43
+ resolve();
44
+ });
45
+ });
46
+
47
+ this.emit('ready');
48
+ }
49
+
50
+ async disconnect(): Promise<void> {
51
+ if (this.server) {
52
+ await new Promise<void>((resolve, reject) => {
53
+ this.server!.close((err) => (err ? reject(err) : resolve()));
54
+ });
55
+ this.server = null;
56
+ }
57
+ this.connected = false;
58
+ console.log('WATI provider disconnected');
59
+ }
60
+
61
+ async sendMessage(to: string, message: OutgoingMessage): Promise<void> {
62
+ if (!this.connected) {
63
+ throw new Error('WATI provider not connected');
64
+ }
65
+
66
+ await this.apiRequest(`/v1/sendSessionMessage/${to}`, {
67
+ method: 'POST',
68
+ body: JSON.stringify({ messageText: message.text }),
69
+ });
70
+
71
+ console.log(`šŸ“¤ [WATI] Sent reply to ${to}`);
72
+ }
73
+
74
+ async sendTemplateMessage(to: string, template: WatiTemplateParams): Promise<void> {
75
+ if (!this.connected) {
76
+ throw new Error('WATI provider not connected');
77
+ }
78
+
79
+ await this.apiRequest(`/v2/sendTemplateMessage?whatsappNumber=${encodeURIComponent(to)}`, {
80
+ method: 'POST',
81
+ body: JSON.stringify({
82
+ template_name: template.templateName,
83
+ parameters: template.parameters,
84
+ }),
85
+ });
86
+
87
+ console.log(`šŸ“¤ [WATI] Sent template "${template.templateName}" to ${to}`);
88
+ }
89
+
90
+ isActive(): boolean {
91
+ return this.connected;
92
+ }
93
+
94
+ // --- Private helpers ---
95
+
96
+ private handleVerification(req: http.IncomingMessage, res: http.ServerResponse): void {
97
+ const url = new URL(req.url!, `http://localhost:${this.webhookPort}`);
98
+ const challenge = url.searchParams.get('hub.challenge');
99
+ if (challenge) {
100
+ res.writeHead(200, { 'Content-Type': 'text/plain' }).end(challenge);
101
+ } else {
102
+ res.writeHead(200).end('ok');
103
+ }
104
+ }
105
+
106
+ private handleWebhookRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
107
+ let body = '';
108
+ req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
109
+ req.on('end', () => {
110
+ res.writeHead(200).end('ok');
111
+
112
+ try {
113
+ const payload: WatiWebhookPayload = JSON.parse(body);
114
+
115
+ if (payload.eventType !== 'messageReceived' || payload.fromMe) {
116
+ return;
117
+ }
118
+
119
+ const msg: IncomingMessage = {
120
+ id: payload.whatsappMessageId,
121
+ from: payload.waId,
122
+ text: payload.text,
123
+ timestamp: new Date(payload.timestamp).getTime(),
124
+ channel: 'whatsapp',
125
+ provider: this.name,
126
+ metadata: {
127
+ senderName: payload.senderName,
128
+ messageType: payload.type,
129
+ },
130
+ };
131
+
132
+ console.log(`\nšŸ“„ [WATI] New message from ${payload.senderName || payload.waId}`);
133
+ console.log(` Message: "${msg.text}"`);
134
+
135
+ this.emit('message', msg);
136
+ } catch (err) {
137
+ console.error('āŒ [WATI] Failed to parse webhook payload:', err);
138
+ }
139
+ });
140
+ }
141
+
142
+ private async apiRequest(path: string, init: RequestInit): Promise<Response> {
143
+ const url = `${this.baseUrl}${path}`;
144
+ const res = await fetch(url, {
145
+ ...init,
146
+ headers: {
147
+ 'Authorization': `Bearer ${this.config.apiToken}`,
148
+ 'Content-Type': 'application/json',
149
+ ...init.headers,
150
+ },
151
+ });
152
+
153
+ if (!res.ok) {
154
+ const text = await res.text().catch(() => '');
155
+ throw new Error(`WATI API error ${res.status}: ${text}`);
156
+ }
157
+
158
+ return res;
159
+ }
160
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { WatiProvider } from './WatiProvider.js';
2
+ export type { WatiProviderConfig, WatiTemplateParams } from './types.js';
package/src/types.ts ADDED
@@ -0,0 +1,28 @@
1
+ export interface WatiProviderConfig {
2
+ /** WATI API bearer token */
3
+ apiToken: string;
4
+ /** WATI tenant ID (appears in the API base URL) */
5
+ tenantId: string;
6
+ /** Port for the webhook HTTP server (default: 3001) */
7
+ webhookPort?: number;
8
+ /** Path the webhook listens on (default: /webhook/wati) */
9
+ webhookPath?: string;
10
+ /** Override the WATI API base URL (default: https://live-mt-server.wati.io/{tenantId}/api) */
11
+ apiBaseUrl?: string;
12
+ }
13
+
14
+ export interface WatiWebhookPayload {
15
+ eventType: string;
16
+ waId: string;
17
+ text: string;
18
+ whatsappMessageId: string;
19
+ timestamp: string;
20
+ senderName: string;
21
+ type: string;
22
+ fromMe: boolean;
23
+ }
24
+
25
+ export interface WatiTemplateParams {
26
+ templateName: string;
27
+ parameters: Array<{ name: string; value: string }>;
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ outExtensions: () => ({ js: '.js', dts: '.d.ts' }),
10
+ });