andiwagateway 1.0.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,151 @@
1
+ # Panduan Publish SDK ke NPM
2
+
3
+ ## 🚀 Langkah-langkah Publish ke NPM
4
+
5
+ ### 1. Daftar Akun NPM (Gratis)
6
+
7
+ 1. Buka https://www.npmjs.com/signup
8
+ 2. Buat akun dengan username (contoh: `andiwa`)
9
+ 3. Verifikasi email
10
+
11
+ ### 2. Login di Terminal
12
+
13
+ ```bash
14
+ cd /Users/andiyanto/Desktop/qr-wa/andi-wa-gateway-sdk
15
+ npm login
16
+ ```
17
+ Masukkan:
18
+ - Username: (username NPM Anda)
19
+ - Password: (password NPM Anda)
20
+ - Email: (email verifikasi)
21
+ - OTP: (kode dari email/authenticator)
22
+
23
+ ### 3. Test Package (Dry Run)
24
+
25
+ ```bash
26
+ npm publish --dry-run
27
+ ```
28
+ Ini akan simulasi publish tanpa benar-benar upload. Cek apakah ada error.
29
+
30
+ ### 4. Publish (Public - Gratis)
31
+
32
+ ```bash
33
+ npm publish --access public
34
+ ```
35
+
36
+ **Sukses!** SDK sekarang ada di: `https://www.npmjs.com/package/andi-wa-gateway-sdk`
37
+
38
+ ---
39
+
40
+ ## 🔄 Update SDK (Versi Baru)
41
+
42
+ Kalau ada perubahan di SDK, update versi dulu:
43
+
44
+ ### Otomatis (Recommended)
45
+
46
+ ```bash
47
+ # Update patch version (1.0.0 -> 1.0.1)
48
+ npm version patch
49
+
50
+ # Update minor version (1.0.0 -> 1.1.0)
51
+ npm version minor
52
+
53
+ # Update major version (1.0.0 -> 2.0.0)
54
+ npm version major
55
+ ```
56
+
57
+ Lalu publish:
58
+ ```bash
59
+ npm publish
60
+ ```
61
+
62
+ ### Manual
63
+
64
+ Edit `package.json`, ubah version:
65
+ ```json
66
+ "version": "1.0.1"
67
+ ```
68
+
69
+ Lalu:
70
+ ```bash
71
+ npm publish
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 📦 Developer Cara Install
77
+
78
+ Setelah publish, developer bisa:
79
+
80
+ ```bash
81
+ npm install andi-wa-gateway-sdk
82
+ ```
83
+
84
+ Lalu pakai:
85
+ ```javascript
86
+ const sdk = require('andi-wa-gateway-sdk');
87
+
88
+ const wa = sdk.createClient({
89
+ apiKey: 'ak_live_xxxx'
90
+ });
91
+
92
+ await wa.sendMessage('628123456789', 'Halo!');
93
+ ```
94
+
95
+ ---
96
+
97
+ ## ❌ Unpublish (Kalau ada masalah)
98
+
99
+ **Hati-hati!** Hanya bisa unpublish dalam 24 jam pertama:
100
+ ```bash
101
+ npm unpublish andi-wa-gateway-sdk --force
102
+ ```
103
+
104
+ Setelah 24 jam, harus kontak NPM support.
105
+
106
+ ---
107
+
108
+ ## ✅ Checklist Sebelum Publish
109
+
110
+ ```
111
+ □ Nama package unik (andi-wa-gateway-sdk)
112
+ □ Version benar (1.0.0)
113
+ □ Description jelas
114
+ □ Keywords lengkap
115
+ □ Repository URL benar (opsional)
116
+ □ License MIT
117
+ □ .npmignore ada (exclude examples/)
118
+ □ axios di dependencies
119
+ □ Test dry-run berhasil
120
+ □ npm login berhasil
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 🆘 Troubleshooting
126
+
127
+ ### Error: "You do not have permission"
128
+ ```bash
129
+ # Pastikan login npm whoami
130
+ # Kalau tidak keluar username, login ulang
131
+ npm login
132
+ ```
133
+
134
+ ### Error: "Package name too similar"
135
+ ```bash
136
+ # Ganti nama di package.json
137
+ "name": "andi-wa-gateway-sdk-v2"
138
+ ```
139
+
140
+ ### Error: "Failed to replace blob"
141
+ ```bash
142
+ # Bersihkan cache
143
+ npm cache clean --force
144
+ ```
145
+
146
+ ---
147
+
148
+ ## 📞 Butuh Bantuan?
149
+
150
+ - Dokumentasi NPM: https://docs.npmjs.com/
151
+ - Support: https://www.npmjs.com/support
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Andi WA Gateway SDK
2
+
3
+ > **SDK Official untuk Andi WA Gateway**
4
+ >
5
+ > Tinggal pasang API Key, langsung kirim pesan WhatsApp!
6
+
7
+ ---
8
+
9
+ ## 🎯 Fitur
10
+
11
+ - ✅ **API Key Only** - Tidak perlu setup server, tinggal API Key
12
+ - ✅ **Base URL Built-in** - Sudah terkonfigurasi ke `andi-wa-gateway.up.railway.app`
13
+ - ✅ **OTP Plugin** - Kirim kode verifikasi dengan 1 baris kode
14
+ - ✅ **Webhook Handler** - Terima pesan masuk dengan mudah
15
+ - ✅ **Express Ready** - Middleware siap pakai
16
+
17
+ ---
18
+
19
+ ## 📦 Install
20
+
21
+ ```bash
22
+ npm install andi-wa-gateway-sdk
23
+ ```
24
+
25
+ ---
26
+
27
+ ## 🚀 Quick Start
28
+
29
+ ### 1. Kirim Pesan (Basic)
30
+
31
+ ```javascript
32
+ const sdk = require('andi-wa-gateway-sdk');
33
+
34
+ // Inisialisasi - Cukup API Key!
35
+ const wa = sdk.createClient({
36
+ apiKey: 'ak_live_abc123xyz789' // Dari Panel Andi WA Gateway
37
+ });
38
+
39
+ // Kirim pesan
40
+ await wa.sendMessage('628123456789', 'Halo dari SDK!');
41
+ ```
42
+
43
+ ### 2. Kirim OTP (Plugin)
44
+
45
+ ```javascript
46
+ const sdk = require('andi-wa-gateway-sdk');
47
+
48
+ const otp = sdk.createOtp({
49
+ apiKey: 'ak_live_abc123xyz789'
50
+ });
51
+
52
+ // Kirim OTP
53
+ const result = await otp.send('628123456789', {
54
+ appName: 'MyApp'
55
+ });
56
+
57
+ console.log('Kode OTP:', result.code); // Simpan ke database!
58
+
59
+ // Verifikasi
60
+ const isValid = otp.verify(inputUser, result.code);
61
+ ```
62
+
63
+ ### 3. Webhook Handler (Terima Pesan)
64
+
65
+ ```javascript
66
+ const sdk = require('andi-wa-gateway-sdk');
67
+ const webhook = sdk.createWebhook();
68
+
69
+ // Handler pesan masuk
70
+ webhook.onMessage(async (msg) => {
71
+ console.log('Dari:', msg.from);
72
+ console.log('Isi:', msg.body);
73
+ });
74
+
75
+ // Express middleware
76
+ app.post('/webhook/whatsapp', webhook.middleware());
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 📚 API Reference
82
+
83
+ ### `createClient(config)`
84
+
85
+ Membuat instance client utama.
86
+
87
+ ```javascript
88
+ const wa = sdk.createClient({
89
+ apiKey: 'your-api-key', // Required
90
+ sessionId: 'default-session', // Optional
91
+ baseUrl: 'https://custom-url.com' // Optional (override default)
92
+ });
93
+ ```
94
+
95
+ **Methods:**
96
+
97
+ | Method | Description |
98
+ |--------|-------------|
99
+ | `wa.sendMessage(number, message, sessionId?)` | Kirim pesan teks |
100
+ | `wa.sendMedia(number, mediaUrl, caption?, sessionId?)` | Kirim file/media |
101
+ | `wa.getStatus(sessionId?)` | Cek status session |
102
+ | `wa.listSessions()` | Daftar semua session |
103
+ | `wa.registerWebhook(url, events?)` | Register webhook URL |
104
+
105
+ ### `createOtp(config)`
106
+
107
+ Plugin untuk kirim OTP.
108
+
109
+ ```javascript
110
+ const otp = sdk.createOtp({
111
+ apiKey: 'your-api-key',
112
+ sessionId: 'otp-session'
113
+ });
114
+ ```
115
+
116
+ **Methods:**
117
+
118
+ | Method | Description |
119
+ |--------|-------------|
120
+ | `otp.generateCode(length?)` | Generate kode random |
121
+ | `otp.send(number, options?)` | Kirim OTP |
122
+ | `otp.sendWithTemplate(number, template, options?)` | Kirim dengan template custom |
123
+ | `otp.verify(input, stored)` | Verifikasi kode |
124
+
125
+ ### `createWebhook(config)`
126
+
127
+ Handler untuk webhook.
128
+
129
+ ```javascript
130
+ const webhook = sdk.createWebhook({
131
+ secret: 'optional-secret' // Untuk verify signature
132
+ });
133
+ ```
134
+
135
+ **Methods:**
136
+
137
+ | Method | Description |
138
+ |--------|-------------|
139
+ | `webhook.on(event, handler)` | Register handler untuk event |
140
+ | `webhook.onMessage(handler)` | Handler khusus pesan masuk |
141
+ | `webhook.middleware()` | Express middleware |
142
+ | `webhook.createRouter()` | Express router instance |
143
+
144
+ ---
145
+
146
+ ## 💡 Contoh Lengkap
147
+
148
+ Lihat folder `examples/`:
149
+
150
+ - `basic-usage.js` - Kirim pesan & media
151
+ - `otp-plugin.js` - Sistem verifikasi OTP
152
+ - `express-webhook.js` - Express server dengan webhook
153
+
154
+ ---
155
+
156
+ ## 🔧 Environment Variables
157
+
158
+ ```env
159
+ WA_GATEWAY_API_KEY=ak_live_abc123xyz789
160
+ ```
161
+
162
+ ---
163
+
164
+ ## 📞 Support
165
+
166
+ - Panel Admin: https://andi-wa-gateway.up.railway.app
167
+ - API Docs: Lihat dokumentasi di panel
168
+
169
+ ---
170
+
171
+ ## 📄 License
172
+
173
+ MIT
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "andiwagateway",
3
+ "version": "1.0.0",
4
+ "description": "Official SDK for Andi WA Gateway - WhatsApp API integration made simple. Just add API Key and start sending messages.",
5
+ "main": "src/index.js",
6
+ "keywords": [
7
+ "whatsapp",
8
+ "gateway",
9
+ "sdk",
10
+ "andi",
11
+ "otp",
12
+ "notification",
13
+ "webhook",
14
+ "baileys",
15
+ "messaging",
16
+ "api"
17
+ ],
18
+ "author": "Andi <andi@andi-wa-gateway.com>",
19
+ "license": "MIT",
20
+ "type": "commonjs",
21
+ "engines": {
22
+ "node": ">=16.0.0"
23
+ },
24
+ "dependencies": {
25
+ "axios": "^1.6.0"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/andi/andi-wa-gateway-sdk.git"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/andi/andi-wa-gateway-sdk/issues"
33
+ },
34
+ "homepage": "https://github.com/andi/andi-wa-gateway-sdk#readme",
35
+ "scripts": {
36
+ "test": "echo 'Error: no test specified' && exit 1"
37
+ }
38
+ }
@@ -0,0 +1,65 @@
1
+ const createHttpClient = require('./http-client');
2
+
3
+ /**
4
+ * Main SDK Client
5
+ * File ini adalah entry point untuk semua operasi SDK
6
+ */
7
+
8
+ function createClient(config) {
9
+ const http = createHttpClient(config);
10
+
11
+ return {
12
+ config,
13
+ http,
14
+
15
+ /**
16
+ * Kirim pesan teks WhatsApp
17
+ * @param {string} number - Nomor tujuan (format: 628123456789 atau 08123456789)
18
+ * @param {string} message - Isi pesan
19
+ * @param {string} [sessionId] - Override session ID (opsional)
20
+ */
21
+ async sendMessage(number, message, sessionId = null) {
22
+ const targetSession = sessionId || config.sessionId;
23
+ return http.sendMessage(targetSession, number, message);
24
+ },
25
+
26
+ /**
27
+ * Kirim media/file WhatsApp
28
+ * @param {string} number - Nomor tujuan
29
+ * @param {string} mediaUrl - URL file/media
30
+ * @param {string} [caption] - Keterangan media
31
+ * @param {string} [sessionId] - Override session ID
32
+ */
33
+ async sendMedia(number, mediaUrl, caption = '', sessionId = null) {
34
+ const targetSession = sessionId || config.sessionId;
35
+ return http.sendMedia(targetSession, number, mediaUrl, caption);
36
+ },
37
+
38
+ /**
39
+ * Cek status session WhatsApp
40
+ * @param {string} [sessionId] - Session ID (default: session dari config)
41
+ */
42
+ async getStatus(sessionId = null) {
43
+ const targetSession = sessionId || config.sessionId;
44
+ return http.getSessionStatus(targetSession);
45
+ },
46
+
47
+ /**
48
+ * Daftar semua session
49
+ */
50
+ async listSessions() {
51
+ return http.getSessions();
52
+ },
53
+
54
+ /**
55
+ * Register webhook untuk terima pesan masuk
56
+ * @param {string} webhookUrl - URL endpoint webhook Anda
57
+ * @param {Array} [events] - Event yang didengar (default: ['messages.upsert'])
58
+ */
59
+ async registerWebhook(webhookUrl, events = ['messages.upsert']) {
60
+ return http.registerWebhook(webhookUrl, events);
61
+ }
62
+ };
63
+ }
64
+
65
+ module.exports = createClient;
@@ -0,0 +1,71 @@
1
+ const axios = require('axios');
2
+
3
+ /**
4
+ * HTTP Client untuk Andi WA Gateway
5
+ * File ini handle semua HTTP request ke API
6
+ */
7
+
8
+ function createHttpClient(config) {
9
+ const instance = axios.create({
10
+ baseURL: `${config.baseUrl}/api`,
11
+ headers: {
12
+ 'Authorization': `x-api-key ${config.apiKey}`,
13
+ 'Content-Type': 'application/json'
14
+ },
15
+ timeout: 30000
16
+ });
17
+
18
+ return {
19
+ async sendMessage(sessionId, number, message) {
20
+ const response = await instance.post('/messages/send', {
21
+ sessionId,
22
+ number: formatNumber(number),
23
+ message
24
+ });
25
+ return response.data;
26
+ },
27
+
28
+ async sendMedia(sessionId, number, mediaUrl, caption = '') {
29
+ const response = await instance.post('/messages/send-media', {
30
+ sessionId,
31
+ number: formatNumber(number),
32
+ mediaUrl,
33
+ caption
34
+ });
35
+ return response.data;
36
+ },
37
+
38
+ async getSessionStatus(sessionId) {
39
+ const response = await instance.get(`/session/status/${sessionId}`);
40
+ return response.data;
41
+ },
42
+
43
+ async getSessions() {
44
+ const response = await instance.get('/sessions');
45
+ return response.data;
46
+ },
47
+
48
+ async registerWebhook(url, events = ['messages.upsert']) {
49
+ const response = await instance.post('/webhook/register', {
50
+ url,
51
+ events
52
+ });
53
+ return response.data;
54
+ },
55
+
56
+ async getApiKeys() {
57
+ const response = await instance.get('/keys');
58
+ return response.data;
59
+ }
60
+ };
61
+ }
62
+
63
+ function formatNumber(number) {
64
+ let clean = String(number).replace(/[\s\+-]/g, '');
65
+ if (clean.startsWith('0')) {
66
+ clean = '62' + clean.substring(1);
67
+ }
68
+ return clean;
69
+ }
70
+
71
+ module.exports = createHttpClient;
package/src/index.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Andi WA Gateway SDK
3
+ * Official SDK untuk integrasi dengan Andi WA Gateway
4
+ *
5
+ * Usage:
6
+ * const sdk = require('andi-wa-gateway-sdk');
7
+ * const wa = sdk.createClient({ apiKey: 'your-api-key' });
8
+ * await wa.sendMessage('628123456789', 'Hello!');
9
+ */
10
+
11
+ const createClient = require('./core/client');
12
+ const createOtpPlugin = require('./plugins/otp');
13
+ const createWebhookHandler = require('./webhook/handler');
14
+
15
+ const DEFAULT_BASE_URL = 'https://andi-wa-gateway.up.railway.app';
16
+
17
+ /**
18
+ * Create SDK Client
19
+ * @param {Object} config
20
+ * @param {string} config.apiKey - API Key dari Andi WA Gateway Panel
21
+ * @param {string} [config.baseUrl] - Optional: Override base URL (default: https://andi-wa-gateway.up.railway.app)
22
+ * @param {string} [config.sessionId] - Default session ID untuk kirim pesan
23
+ * @returns {Object} SDK Client instance
24
+ */
25
+ function createClient(config = {}) {
26
+ if (!config.apiKey) {
27
+ throw new Error('API Key wajib diisi. Dapatkan dari Panel Andi WA Gateway.');
28
+ }
29
+
30
+ const finalConfig = {
31
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
32
+ apiKey: config.apiKey,
33
+ sessionId: config.sessionId || 'default-session'
34
+ };
35
+
36
+ return createClient(finalConfig);
37
+ }
38
+
39
+ /**
40
+ * Create OTP Plugin
41
+ * @param {Object} config
42
+ * @param {string} config.apiKey - API Key
43
+ * @param {string} [config.sessionId] - Session ID
44
+ * @returns {Object} OTP Plugin instance
45
+ */
46
+ function createOtp(config = {}) {
47
+ if (!config.apiKey) {
48
+ throw new Error('API Key wajib diisi.');
49
+ }
50
+
51
+ const finalConfig = {
52
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
53
+ apiKey: config.apiKey,
54
+ sessionId: config.sessionId || 'default-session'
55
+ };
56
+
57
+ return createOtpPlugin(finalConfig);
58
+ }
59
+
60
+ /**
61
+ * Create Webhook Handler
62
+ * @param {Object} config
63
+ * @param {string} [config.secret] - Webhook secret untuk verifikasi
64
+ * @returns {Object} Webhook Handler instance
65
+ */
66
+ function createWebhook(config = {}) {
67
+ return createWebhookHandler(config);
68
+ }
69
+
70
+ module.exports = {
71
+ createClient,
72
+ createOtp,
73
+ createWebhook,
74
+ DEFAULT_BASE_URL,
75
+ // Export webhook constants untuk akses mudah
76
+ WEBHOOK_EVENTS: createWebhookHandler.WEBHOOK_EVENTS,
77
+ PAYLOAD_PARSERS: createWebhookHandler.PAYLOAD_PARSERS
78
+ };
@@ -0,0 +1,105 @@
1
+ const createHttpClient = require('../core/http-client');
2
+
3
+ /**
4
+ * OTP Plugin untuk Andi WA Gateway
5
+ * Plugin khusus untuk mengirim kode OTP/verifikasi
6
+ */
7
+
8
+ function createOtpPlugin(config) {
9
+ const http = createHttpClient(config);
10
+
11
+ return {
12
+ config,
13
+
14
+ /**
15
+ * Generate kode OTP random
16
+ * @param {number} [length] - Panjang kode (default: 6)
17
+ * @returns {string} Kode OTP
18
+ */
19
+ generateCode(length = 6) {
20
+ const digits = '0123456789';
21
+ let code = '';
22
+ for (let i = 0; i < length; i++) {
23
+ code += digits[Math.floor(Math.random() * digits.length)];
24
+ }
25
+ return code;
26
+ },
27
+
28
+ /**
29
+ * Format pesan OTP (bisa di-override)
30
+ * @param {string} code - Kode OTP
31
+ * @param {string} [appName] - Nama aplikasi
32
+ * @returns {string} Pesan yang diformat
33
+ */
34
+ formatMessage(code, appName = 'Aplikasi') {
35
+ return `Kode verifikasi ${appName}: ${code}\n\n` +
36
+ `Jangan berikan kode ini kepada siapapun.\n` +
37
+ `Kode berlaku 5 menit.`;
38
+ },
39
+
40
+ /**
41
+ * Kirim OTP ke nomor WhatsApp
42
+ * @param {string} number - Nomor tujuan
43
+ * @param {Object} [options]
44
+ * @param {string} [options.code] - Kode OTP (auto-generate jika tidak diisi)
45
+ * @param {string} [options.appName] - Nama aplikasi untuk pesan
46
+ * @param {string} [options.sessionId] - Override session ID
47
+ * @returns {Promise<Object>} Response dari API + kode yang dikirim
48
+ */
49
+ async send(number, options = {}) {
50
+ const code = options.code || this.generateCode();
51
+ const message = this.formatMessage(code, options.appName);
52
+ const sessionId = options.sessionId || config.sessionId;
53
+
54
+ const response = await http.sendMessage(sessionId, number, message);
55
+
56
+ return {
57
+ ...response,
58
+ code, // Return kode untuk disimpan di database
59
+ number: formatNumber(number)
60
+ };
61
+ },
62
+
63
+ /**
64
+ * Kirim OTP dengan template custom
65
+ * @param {string} number - Nomor tujuan
66
+ * @param {string} template - Template pesan dengan placeholder {code}
67
+ * @param {Object} [options]
68
+ * @param {string} [options.code] - Kode OTP
69
+ * @param {string} [options.sessionId] - Override session ID
70
+ */
71
+ async sendWithTemplate(number, template, options = {}) {
72
+ const code = options.code || this.generateCode();
73
+ const message = template.replace(/{code}/g, code);
74
+ const sessionId = options.sessionId || config.sessionId;
75
+
76
+ const response = await http.sendMessage(sessionId, number, message);
77
+
78
+ return {
79
+ ...response,
80
+ code,
81
+ number: formatNumber(number)
82
+ };
83
+ },
84
+
85
+ /**
86
+ * Verify kode OTP (logic di sisi client/database)
87
+ * @param {string} inputCode - Kode yang diinput user
88
+ * @param {string} storedCode - Kode yang tersimpan di database
89
+ * @returns {boolean} Valid atau tidak
90
+ */
91
+ verify(inputCode, storedCode) {
92
+ return String(inputCode) === String(storedCode);
93
+ }
94
+ };
95
+ }
96
+
97
+ function formatNumber(number) {
98
+ let clean = String(number).replace(/[\s\+-]/g, '');
99
+ if (clean.startsWith('0')) {
100
+ clean = '62' + clean.substring(1);
101
+ }
102
+ return clean;
103
+ }
104
+
105
+ module.exports = createOtpPlugin;
@@ -0,0 +1,625 @@
1
+ /**
2
+ * Webhook Handler untuk Andi WA Gateway
3
+ *
4
+ * Menangani 35 Event Webhook dengan 63+ Field Payload
5
+ * Kategori Event:
6
+ * - Pesan & Media (8 event)
7
+ * - Kontak & Profil (3 event)
8
+ * - Chat & Grup (11 event)
9
+ * - Notifikasi & Status (3 event)
10
+ * - Session & Koneksi (6 event)
11
+ * - Blocklist & Broadcast (2 event)
12
+ * - Newsletter & Community (2 event)
13
+ * - Payment (1 event)
14
+ */
15
+
16
+ // ============================================================================
17
+ // WEBHOOK EVENT CONSTANTS (35 Events)
18
+ // ============================================================================
19
+
20
+ const WEBHOOK_EVENTS = {
21
+ // Pesan & Media (8 events)
22
+ MESSAGES_UPSERT: 'messages.upsert',
23
+ MESSAGE_STATUS: 'message.status',
24
+ MESSAGE_REACTION: 'message.reaction',
25
+ MESSAGE_POLL_VOTE: 'message.poll_vote',
26
+ MESSAGE_EDIT: 'message.edit',
27
+ MESSAGE_REVOKE: 'message.revoke',
28
+ MESSAGE_DELETE: 'message.delete',
29
+ MESSAGE_HISTORY_SYNC: 'message.history_sync',
30
+
31
+ // Kontak & Profil (3 events)
32
+ CONTACT_UPSERT: 'contact.upsert',
33
+ CONTACT_UPDATE: 'contact.update',
34
+ PRESENCE_UPDATE: 'presence.update',
35
+
36
+ // Chat & Grup (11 events)
37
+ CHAT_UPSERT: 'chat.upsert',
38
+ CHAT_UPDATE: 'chat.update',
39
+ CHAT_PIN: 'chat.pin',
40
+ CHAT_UNPIN: 'chat.unpin',
41
+ CHAT_ARCHIVE: 'chat.archive',
42
+ CHAT_UNARCHIVE: 'chat.unarchive',
43
+ GROUP_PARTICIPANTS: 'group.participants',
44
+ GROUP_UPDATE: 'group.update',
45
+ GROUP_MEMBERSHIP_REQUEST: 'group.membership_request',
46
+ GROUP_JOIN: 'group.join',
47
+ GROUP_LEAVE: 'group.leave',
48
+
49
+ // Notifikasi & Status (3 events)
50
+ CALL_NOTIFICATION: 'call.notification',
51
+ STATUS_UPDATE: 'status.update',
52
+ LABELS_UPDATE: 'labels.update',
53
+
54
+ // Session & Koneksi (6 events)
55
+ CONNECTION_UPDATE: 'connection.update',
56
+ CREDS_UPDATE: 'creds.update',
57
+ QR_GENERATED: 'qr.generated',
58
+ SESSION_LOGOUT: 'session.logout',
59
+ SYNC_REQUIRED: 'sync.required',
60
+ MESSAGE_MEDIA_DOWNLOAD: 'message.media_download',
61
+
62
+ // Blocklist & Broadcast (2 events)
63
+ BLOCKLIST_UPDATE: 'blocklist.update',
64
+ BROADCAST_UPDATE: 'broadcast.update',
65
+
66
+ // Newsletter & Community (2 events)
67
+ NEWSLETTER_UPDATE: 'newsletter.update',
68
+ COMMUNITY_UPDATE: 'community.update',
69
+
70
+ // Payment (1 event)
71
+ PAYMENT_UPDATE: 'payment.update'
72
+ };
73
+
74
+ // ============================================================================
75
+ // PAYLOAD PARSER - 63+ Field Support
76
+ // ============================================================================
77
+
78
+ /**
79
+ * Parse payload messages.upsert (Pesan Masuk)
80
+ * Field yang tersedia: 25+ field
81
+ */
82
+ function parseMessagePayload(data) {
83
+ return {
84
+ // Identitas
85
+ sessionId: data.sessionId,
86
+ device_id: data.device_id,
87
+ device: data.device,
88
+
89
+ // Pengirim
90
+ from: data.from,
91
+ participant: data.participant,
92
+ push_name: data.push_name,
93
+
94
+ // Pesan
95
+ message_id: data.message_id,
96
+ message_type: data.message_type,
97
+ message: data.message,
98
+
99
+ // Chat Info
100
+ chat_type: data.chat_type,
101
+ is_group: data.is_group,
102
+ is_me: data.is_me,
103
+
104
+ // Status Pesan
105
+ is_view_once: data.is_view_once,
106
+ is_ephemeral: data.is_ephemeral,
107
+ is_business: data.is_business,
108
+ is_marketing_keyword: data.is_marketing_keyword,
109
+ is_mentioned: data.is_mentioned,
110
+
111
+ // Media/File
112
+ file: data.file, // { type, mimetype, fileName, fileSize, caption }
113
+
114
+ // Mentions & Quotes
115
+ mentioned_jids: data.mentioned_jids || [],
116
+ quoted_message: data.quoted_message,
117
+
118
+ // Timestamp
119
+ timestamp: data.timestamp,
120
+ datetime: data.timestamp ? new Date(data.timestamp * 1000) : null
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Parse payload message.status (Status Pesan)
126
+ * Field: message_id, from, status, timestamp
127
+ */
128
+ function parseMessageStatusPayload(data) {
129
+ return {
130
+ message_id: data.message_id,
131
+ from: data.from,
132
+ status: data.status, // 'pending', 'sent', 'delivered', 'read', 'played'
133
+ timestamp: data.timestamp,
134
+ datetime: data.timestamp ? new Date(data.timestamp) : null
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Parse payload presence.update (Status Online/Typing)
140
+ */
141
+ function parsePresencePayload(data) {
142
+ return {
143
+ id: data.id,
144
+ presence: data.presence?.lastKnownPresence, // 'composing', 'recording', 'available', 'unavailable'
145
+ timestamp: data.timestamp
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Parse payload group.participants (Perubahan Anggota Grup)
151
+ */
152
+ function parseGroupParticipantsPayload(data) {
153
+ return {
154
+ group_id: data.group_id,
155
+ action: data.action, // 'add', 'remove', 'promote', 'demote'
156
+ participants: data.participants || [],
157
+ by: data.by, // Siapa yang melakukan aksi
158
+ timestamp: data.timestamp
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Parse payload group.update (Update Info Grup)
164
+ */
165
+ function parseGroupUpdatePayload(data) {
166
+ return {
167
+ group_id: data.group_id,
168
+ subject: data.subject, // Nama grup baru
169
+ description: data.description,
170
+ owner: data.owner,
171
+ participants_count: data.participants_count,
172
+ timestamp: data.timestamp
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Parse payload connection.update (Status Koneksi)
178
+ */
179
+ function parseConnectionPayload(data) {
180
+ return {
181
+ connection: data.connection, // 'connecting', 'open', 'close', 'error'
182
+ isNewLogin: data.isNewLogin,
183
+ qr: data.qr, // QR string jika ada
184
+ receivedPendingNotifications: data.receivedPendingNotifications,
185
+ timestamp: data.timestamp
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Parse payload payment.update (Pembayaran)
191
+ */
192
+ function parsePaymentPayload(data) {
193
+ return {
194
+ payment_id: data.payment_id,
195
+ status: data.status, // 'pending', 'completed', 'failed'
196
+ amount: data.amount,
197
+ currency: data.currency,
198
+ sender: data.sender,
199
+ receiver: data.receiver,
200
+ timestamp: data.timestamp
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Parse payload call.notification (Panggilan)
206
+ */
207
+ function parseCallPayload(data) {
208
+ return {
209
+ call_id: data.call_id,
210
+ from: data.from,
211
+ isVideo: data.isVideo,
212
+ isGroupCall: data.isGroupCall,
213
+ status: data.status, // 'offer', 'accept', 'reject', 'timeout'
214
+ timestamp: data.timestamp
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Parse payload contact.update (Update Kontak)
220
+ */
221
+ function parseContactPayload(data) {
222
+ return {
223
+ id: data.id,
224
+ name: data.name,
225
+ notify: data.notify,
226
+ imgUrl: data.imgUrl,
227
+ status: data.status,
228
+ timestamp: data.timestamp
229
+ };
230
+ }
231
+
232
+ // ============================================================================
233
+ // MAIN WEBHOOK HANDLER
234
+ // ============================================================================
235
+
236
+ function createWebhookHandler(config = {}) {
237
+ const handlers = new Map();
238
+ const middlewares = [];
239
+
240
+ const handler = {
241
+ config,
242
+
243
+ // ========================================================================
244
+ // EVENT REGISTRATION
245
+ // ========================================================================
246
+
247
+ /**
248
+ * Register handler untuk event tertentu
249
+ * @param {string} event - Nama event dari WEBHOOK_EVENTS
250
+ * @param {Function} handler - Function handler (parsedPayload, rawPayload) => void
251
+ */
252
+ on(event, handler) {
253
+ if (!handlers.has(event)) {
254
+ handlers.set(event, []);
255
+ }
256
+ handlers.get(event).push(handler);
257
+ return this; // Chainable
258
+ },
259
+
260
+ /**
261
+ * Register multiple handlers sekaligus
262
+ * @param {Object} handlersMap - { event: handler }
263
+ */
264
+ onMany(handlersMap) {
265
+ Object.entries(handlersMap).forEach(([event, handler]) => {
266
+ this.on(event, handler);
267
+ });
268
+ return this;
269
+ },
270
+
271
+ /**
272
+ * Remove handler untuk event
273
+ */
274
+ off(event, handlerToRemove) {
275
+ if (handlers.has(event)) {
276
+ const eventHandlers = handlers.get(event);
277
+ const index = eventHandlers.indexOf(handlerToRemove);
278
+ if (index > -1) {
279
+ eventHandlers.splice(index, 1);
280
+ }
281
+ }
282
+ return this;
283
+ },
284
+
285
+ // ========================================================================
286
+ // CORE PROCESSING
287
+ // ========================================================================
288
+
289
+ /**
290
+ * Parse payload berdasarkan event type
291
+ */
292
+ parsePayload(event, data) {
293
+ switch (event) {
294
+ case WEBHOOK_EVENTS.MESSAGES_UPSERT:
295
+ return parseMessagePayload(data);
296
+ case WEBHOOK_EVENTS.MESSAGE_STATUS:
297
+ return parseMessageStatusPayload(data);
298
+ case WEBHOOK_EVENTS.PRESENCE_UPDATE:
299
+ return parsePresencePayload(data);
300
+ case WEBHOOK_EVENTS.GROUP_PARTICIPANTS:
301
+ return parseGroupParticipantsPayload(data);
302
+ case WEBHOOK_EVENTS.GROUP_UPDATE:
303
+ return parseGroupUpdatePayload(data);
304
+ case WEBHOOK_EVENTS.CONNECTION_UPDATE:
305
+ return parseConnectionPayload(data);
306
+ case WEBHOOK_EVENTS.PAYMENT_UPDATE:
307
+ return parsePaymentPayload(data);
308
+ case WEBHOOK_EVENTS.CALL_NOTIFICATION:
309
+ return parseCallPayload(data);
310
+ case WEBHOOK_EVENTS.CONTACT_UPSERT:
311
+ case WEBHOOK_EVENTS.CONTACT_UPDATE:
312
+ return parseContactPayload(data);
313
+ default:
314
+ return data; // Return as-is untuk event lain
315
+ }
316
+ },
317
+
318
+ /**
319
+ * Process webhook payload
320
+ */
321
+ async process(rawPayload) {
322
+ const event = rawPayload.event || WEBHOOK_EVENTS.MESSAGES_UPSERT;
323
+ const data = rawPayload.data || rawPayload;
324
+
325
+ // Parse payload sesuai event type
326
+ const parsedPayload = this.parsePayload(event, data);
327
+
328
+ // Jalankan middlewares
329
+ for (const mw of middlewares) {
330
+ await mw(parsedPayload, rawPayload, event);
331
+ }
332
+
333
+ // Jalankan event handlers
334
+ const eventHandlers = handlers.get(event) || [];
335
+ const results = [];
336
+
337
+ for (const handler of eventHandlers) {
338
+ try {
339
+ const result = await handler(parsedPayload, rawPayload);
340
+ results.push({ success: true, result });
341
+ } catch (err) {
342
+ results.push({ success: false, error: err.message });
343
+ }
344
+ }
345
+
346
+ return {
347
+ event,
348
+ processed: results.length,
349
+ results,
350
+ parsed: parsedPayload
351
+ };
352
+ },
353
+
354
+ // ========================================================================
355
+ // EXPRESS MIDDLEWARE
356
+ // ========================================================================
357
+
358
+ /**
359
+ * Express/Fastify middleware
360
+ */
361
+ middleware() {
362
+ return async (req, res, next) => {
363
+ try {
364
+ // Verify secret jika di-configure
365
+ if (config.secret) {
366
+ const secret = req.headers['x-webhook-secret'];
367
+ if (secret !== config.secret) {
368
+ return res.status(401).json({
369
+ error: 'Invalid webhook secret',
370
+ code: 'UNAUTHORIZED'
371
+ });
372
+ }
373
+ }
374
+
375
+ // Verify signature jika di-configure
376
+ if (config.verifySignature && config.signatureKey) {
377
+ const signature = req.headers['x-signature'];
378
+ // TODO: Implement signature verification
379
+ }
380
+
381
+ const rawPayload = req.body;
382
+
383
+ // Validasi payload
384
+ if (!rawPayload || typeof rawPayload !== 'object') {
385
+ return res.status(400).json({
386
+ error: 'Invalid payload',
387
+ code: 'BAD_REQUEST'
388
+ });
389
+ }
390
+
391
+ // Process
392
+ const result = await this.process(rawPayload);
393
+
394
+ res.json({
395
+ success: true,
396
+ processed: result.processed,
397
+ event: result.event
398
+ });
399
+ } catch (err) {
400
+ if (next) {
401
+ next(err);
402
+ } else {
403
+ res.status(500).json({
404
+ error: err.message,
405
+ code: 'INTERNAL_ERROR'
406
+ });
407
+ }
408
+ }
409
+ };
410
+ },
411
+
412
+ /**
413
+ * Add middleware
414
+ */
415
+ use(middleware) {
416
+ middlewares.push(middleware);
417
+ return this;
418
+ },
419
+
420
+ // ========================================================================
421
+ // CONVENIENCE HANDLERS (Shortcuts)
422
+ // ========================================================================
423
+
424
+ /**
425
+ * Handler untuk pesan masuk (messages.upsert)
426
+ * @param {Function} handler - (message) => void
427
+ */
428
+ onMessage(handler) {
429
+ this.on(WEBHOOK_EVENTS.MESSAGES_UPSERT, (parsed, raw) => {
430
+ handler(parsed, raw);
431
+ });
432
+ return this;
433
+ },
434
+
435
+ /**
436
+ * Handler untuk pesan teks saja (filter media)
437
+ */
438
+ onTextMessage(handler) {
439
+ this.on(WEBHOOK_EVENTS.MESSAGES_UPSERT, (parsed, raw) => {
440
+ if (parsed.message_type === 'text' && !parsed.is_me) {
441
+ handler(parsed, raw);
442
+ }
443
+ });
444
+ return this;
445
+ },
446
+
447
+ /**
448
+ * Handler untuk media (image, video, audio, document)
449
+ */
450
+ onMedia(handler) {
451
+ const mediaTypes = ['image', 'video', 'audio', 'document', 'sticker'];
452
+ this.on(WEBHOOK_EVENTS.MESSAGES_UPSERT, (parsed, raw) => {
453
+ if (mediaTypes.includes(parsed.message_type) && !parsed.is_me) {
454
+ handler(parsed, raw);
455
+ }
456
+ });
457
+ return this;
458
+ },
459
+
460
+ /**
461
+ * Handler untuk grup message saja
462
+ */
463
+ onGroupMessage(handler) {
464
+ this.on(WEBHOOK_EVENTS.MESSAGES_UPSERT, (parsed, raw) => {
465
+ if (parsed.is_group && !parsed.is_me) {
466
+ handler(parsed, raw);
467
+ }
468
+ });
469
+ return this;
470
+ },
471
+
472
+ /**
473
+ * Handler untuk private message saja
474
+ */
475
+ onPrivateMessage(handler) {
476
+ this.on(WEBHOOK_EVENTS.MESSAGES_UPSERT, (parsed, raw) => {
477
+ if (!parsed.is_group && !parsed.is_me) {
478
+ handler(parsed, raw);
479
+ }
480
+ });
481
+ return this;
482
+ },
483
+
484
+ /**
485
+ * Handler untuk status pesan (sent/delivered/read)
486
+ */
487
+ onStatusUpdate(handler) {
488
+ this.on(WEBHOOK_EVENTS.MESSAGE_STATUS, handler);
489
+ return this;
490
+ },
491
+
492
+ /**
493
+ * Handler untuk pesan diterima (delivered)
494
+ */
495
+ onDelivered(handler) {
496
+ this.on(WEBHOOK_EVENTS.MESSAGE_STATUS, (parsed, raw) => {
497
+ if (parsed.status === 'delivered') {
498
+ handler(parsed, raw);
499
+ }
500
+ });
501
+ return this;
502
+ },
503
+
504
+ /**
505
+ * Handler untuk pesan dibaca (read)
506
+ */
507
+ onRead(handler) {
508
+ this.on(WEBHOOK_EVENTS.MESSAGE_STATUS, (parsed, raw) => {
509
+ if (parsed.status === 'read') {
510
+ handler(parsed, raw);
511
+ }
512
+ });
513
+ return this;
514
+ },
515
+
516
+ /**
517
+ * Handler untuk kontak sedang mengetik
518
+ */
519
+ onTyping(handler) {
520
+ this.on(WEBHOOK_EVENTS.PRESENCE_UPDATE, (parsed, raw) => {
521
+ if (parsed.presence === 'composing') {
522
+ handler(parsed, raw);
523
+ }
524
+ });
525
+ return this;
526
+ },
527
+
528
+ /**
529
+ * Handler untuk perubahan grup
530
+ */
531
+ onGroupUpdate(handler) {
532
+ this.on(WEBHOOK_EVENTS.GROUP_UPDATE, handler);
533
+ return this;
534
+ },
535
+
536
+ /**
537
+ * Handler untuk member join/leave grup
538
+ */
539
+ onGroupParticipants(handler) {
540
+ this.on(WEBHOOK_EVENTS.GROUP_PARTICIPANTS, handler);
541
+ return this;
542
+ },
543
+
544
+ /**
545
+ * Handler untuk perubahan koneksi
546
+ */
547
+ onConnectionUpdate(handler) {
548
+ this.on(WEBHOOK_EVENTS.CONNECTION_UPDATE, handler);
549
+ return this;
550
+ },
551
+
552
+ /**
553
+ * Handler untuk QR code generated
554
+ */
555
+ onQR(handler) {
556
+ this.on(WEBHOOK_EVENTS.QR_GENERATED, handler);
557
+ return this;
558
+ },
559
+
560
+ /**
561
+ * Handler untuk incoming call
562
+ */
563
+ onCall(handler) {
564
+ this.on(WEBHOOK_EVENTS.CALL_NOTIFICATION, handler);
565
+ return this;
566
+ },
567
+
568
+ /**
569
+ * Handler untuk payment update
570
+ */
571
+ onPayment(handler) {
572
+ this.on(WEBHOOK_EVENTS.PAYMENT_UPDATE, handler);
573
+ return this;
574
+ },
575
+
576
+ /**
577
+ * Handler untuk kontak update
578
+ */
579
+ onContactUpdate(handler) {
580
+ this.on(WEBHOOK_EVENTS.CONTACT_UPDATE, handler);
581
+ return this;
582
+ },
583
+
584
+ // ========================================================================
585
+ // EXPRESS ROUTER
586
+ // ========================================================================
587
+
588
+ /**
589
+ * Create Express router dengan endpoint siap pakai
590
+ */
591
+ createRouter() {
592
+ const express = require('express');
593
+ const router = express.Router();
594
+
595
+ router.post('/', this.middleware());
596
+
597
+ // Health check endpoint
598
+ router.get('/health', (req, res) => {
599
+ res.json({ status: 'ok', handler: 'andi-wa-gateway-webhook' });
600
+ });
601
+
602
+ return router;
603
+ }
604
+ };
605
+
606
+ return handler;
607
+ }
608
+
609
+ // ============================================================================
610
+ // EXPORTS
611
+ // ============================================================================
612
+
613
+ module.exports = createWebhookHandler;
614
+ module.exports.WEBHOOK_EVENTS = WEBHOOK_EVENTS;
615
+ module.exports.PAYLOAD_PARSERS = {
616
+ parseMessagePayload,
617
+ parseMessageStatusPayload,
618
+ parsePresencePayload,
619
+ parseGroupParticipantsPayload,
620
+ parseGroupUpdatePayload,
621
+ parseConnectionPayload,
622
+ parsePaymentPayload,
623
+ parseCallPayload,
624
+ parseContactPayload
625
+ };