@gamastudio/sendwave-provider 1.0.3 → 1.0.5
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/.github/workflows/build.yml +31 -31
- package/README.md +267 -25
- package/build/application/connection/connection-polling.service.d.ts +16 -0
- package/build/application/connection/connection-polling.service.js +49 -0
- package/build/application/message-buffer/message-buffer.service.d.ts +11 -0
- package/build/application/message-buffer/message-buffer.service.js +79 -0
- package/build/application/messaging/message-sender.service.d.ts +40 -0
- package/build/application/messaging/message-sender.service.js +101 -0
- package/build/application/messaging/strategies/interactive.strategy.d.ts +14 -0
- package/build/application/messaging/strategies/interactive.strategy.js +88 -0
- package/build/application/messaging/strategies/list.strategy.d.ts +8 -0
- package/build/application/messaging/strategies/list.strategy.js +61 -0
- package/build/application/messaging/strategies/media.strategy.d.ts +32 -0
- package/build/application/messaging/strategies/media.strategy.js +97 -0
- package/build/application/messaging/strategies/misc.strategy.d.ts +23 -0
- package/build/application/messaging/strategies/misc.strategy.js +95 -0
- package/build/application/messaging/strategies/presence.strategy.d.ts +8 -0
- package/build/application/messaging/strategies/presence.strategy.js +24 -0
- package/build/application/messaging/strategies/send-strategy.interface.d.ts +4 -0
- package/build/application/messaging/strategies/send-strategy.interface.js +2 -0
- package/build/application/messaging/strategies/text.strategy.d.ts +8 -0
- package/build/application/messaging/strategies/text.strategy.js +22 -0
- package/build/application/messaging/strategies/voice.strategy.d.ts +8 -0
- package/build/application/messaging/strategies/voice.strategy.js +37 -0
- package/build/application/queue-flow/queue-flow.service.d.ts +34 -0
- package/build/application/queue-flow/queue-flow.service.js +68 -0
- package/build/config/constants.d.ts +14 -0
- package/build/config/constants.js +17 -0
- package/build/config/defaults.d.ts +8 -0
- package/build/config/defaults.js +110 -0
- package/build/constants/html/error.js +34 -34
- package/build/constants/html/home.js +23 -23
- package/build/constants/svg/wave-error.js +88 -88
- package/build/constants/svg/wave.js +88 -88
- package/build/core/interface/provider.interface.d.ts +2 -1
- package/build/core/interface/types.d.ts +12 -0
- package/build/domain/interfaces/index.d.ts +2 -0
- package/build/domain/interfaces/index.js +18 -0
- package/build/domain/interfaces/parsed-message.interface.d.ts +16 -0
- package/build/domain/interfaces/parsed-message.interface.js +2 -0
- package/build/domain/interfaces/provider.interface.d.ts +21 -0
- package/build/domain/interfaces/provider.interface.js +2 -0
- package/build/domain/types/config.types.d.ts +43 -0
- package/build/domain/types/config.types.js +2 -0
- package/build/domain/types/index.d.ts +2 -0
- package/build/domain/types/index.js +18 -0
- package/build/domain/types/message.types.d.ts +134 -0
- package/build/domain/types/message.types.js +2 -0
- package/build/index.d.ts +5 -4
- package/build/index.js +7 -6
- package/build/infrastructure/http/axios/retry.interceptor.d.ts +2 -0
- package/build/infrastructure/http/axios/retry.interceptor.js +21 -0
- package/build/infrastructure/http/axios/sendwave-api.factory.d.ts +15 -0
- package/build/infrastructure/http/axios/sendwave-api.factory.js +50 -0
- package/build/infrastructure/http/middleware/body-parser.middleware.d.ts +2 -0
- package/build/infrastructure/http/middleware/body-parser.middleware.js +28 -0
- package/build/infrastructure/http/middleware/cors.middleware.d.ts +6 -0
- package/build/infrastructure/http/middleware/cors.middleware.js +8 -0
- package/build/infrastructure/http/middleware/global-args.middleware.d.ts +2 -0
- package/build/infrastructure/http/middleware/global-args.middleware.js +9 -0
- package/build/infrastructure/http/middleware/rate-limiter.middleware.d.ts +4 -0
- package/build/infrastructure/http/middleware/rate-limiter.middleware.js +31 -0
- package/build/infrastructure/http/middleware/static-assets.middleware.d.ts +2 -0
- package/build/infrastructure/http/middleware/static-assets.middleware.js +8 -0
- package/build/infrastructure/http/routes/routes.d.ts +4 -0
- package/build/infrastructure/http/routes/routes.js +56 -0
- package/build/infrastructure/media/media-detector.service.d.ts +27 -0
- package/build/infrastructure/media/media-detector.service.js +123 -0
- package/build/infrastructure/parser/incoming-message.parser.d.ts +2 -0
- package/build/infrastructure/parser/incoming-message.parser.js +41 -0
- package/build/infrastructure/persistence/instance.registry.d.ts +21 -0
- package/build/infrastructure/persistence/instance.registry.js +44 -0
- package/build/infrastructure/persistence/sendwave-api.repository.d.ts +21 -0
- package/build/infrastructure/persistence/sendwave-api.repository.js +64 -0
- package/build/package.json +48 -48
- package/build/presentation/pages/assets/logo.svg.d.ts +1 -0
- package/build/presentation/pages/assets/logo.svg.js +4 -0
- package/build/presentation/pages/assets/wave-error.svg.d.ts +1 -0
- package/build/presentation/pages/assets/wave-error.svg.js +92 -0
- package/build/presentation/pages/assets/wave.svg.d.ts +1 -0
- package/build/presentation/pages/assets/wave.svg.js +92 -0
- package/build/presentation/pages/connect.page.d.ts +1 -0
- package/build/presentation/pages/connect.page.js +182 -0
- package/build/presentation/pages/error.page.d.ts +1 -0
- package/build/presentation/pages/error.page.js +42 -0
- package/build/presentation/pages/home.page.d.ts +1 -0
- package/build/presentation/pages/home.page.js +29 -0
- package/build/presentation/provider/index.d.ts +2 -0
- package/build/presentation/provider/index.js +18 -0
- package/build/presentation/provider/sendwave-core.d.ts +25 -0
- package/build/presentation/provider/sendwave-core.js +125 -0
- package/build/presentation/provider/sendwave.provider.d.ts +76 -0
- package/build/presentation/provider/sendwave.provider.js +359 -0
- package/build/provider/sender.d.ts +3 -1
- package/build/provider/sender.js +52 -34
- package/build/shared/exceptions/payload-too-large.exception.d.ts +4 -0
- package/build/shared/exceptions/payload-too-large.exception.js +11 -0
- package/build/shared/utils/error-extractor.d.ts +1 -0
- package/build/shared/utils/error-extractor.js +7 -0
- package/build/shared/utils/size-parser.d.ts +1 -0
- package/build/shared/utils/size-parser.js +17 -0
- package/package.json +48 -48
- package/tsconfig.json +7 -5
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
name: Build and Publish to NPM
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
build-and-publish:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- name: 📥 Checkout code
|
|
14
|
-
uses: actions/checkout@v3
|
|
15
|
-
|
|
16
|
-
- name: 🧰 Set up Node.js
|
|
17
|
-
uses: actions/setup-node@v3
|
|
18
|
-
with:
|
|
19
|
-
node-version: 18
|
|
20
|
-
registry-url: https://registry.npmjs.org/
|
|
21
|
-
|
|
22
|
-
- name: 📦 Install dependencies
|
|
23
|
-
run: npm install
|
|
24
|
-
|
|
25
|
-
- name: 🛠️ Build project
|
|
26
|
-
run: npm run build
|
|
27
|
-
|
|
28
|
-
- name: 🚀 Publish to npm
|
|
29
|
-
run: npm publish --access public
|
|
30
|
-
env:
|
|
31
|
-
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
|
1
|
+
name: Build and Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-and-publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: 📥 Checkout code
|
|
14
|
+
uses: actions/checkout@v3
|
|
15
|
+
|
|
16
|
+
- name: 🧰 Set up Node.js
|
|
17
|
+
uses: actions/setup-node@v3
|
|
18
|
+
with:
|
|
19
|
+
node-version: 18
|
|
20
|
+
registry-url: https://registry.npmjs.org/
|
|
21
|
+
|
|
22
|
+
- name: 📦 Install dependencies
|
|
23
|
+
run: npm install
|
|
24
|
+
|
|
25
|
+
- name: 🛠️ Build project
|
|
26
|
+
run: npm run build
|
|
27
|
+
|
|
28
|
+
- name: 🚀 Publish to npm
|
|
29
|
+
run: npm publish --access public
|
|
30
|
+
env:
|
|
31
|
+
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
package/README.md
CHANGED
|
@@ -25,6 +25,91 @@ const provider = createSendWaveProvider({
|
|
|
25
25
|
await provider.initAll(provider.globalVendorArgs.port);
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### Multi-Tenant: Multiple Instances with One Webhook
|
|
29
|
+
|
|
30
|
+
You can manage multiple Sendwave instances with a single provider. The webhook will route messages automatically based on `instanceName` in the payload.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createSendWaveProvider } from "@gamastudio/sendwave-provider";
|
|
34
|
+
|
|
35
|
+
const provider = createSendWaveProvider({
|
|
36
|
+
port: 3000,
|
|
37
|
+
url: "https://msg.sendwaves.cloud",
|
|
38
|
+
instances: [
|
|
39
|
+
{ name: "bot-sales", apiKey: "api-key-sales" },
|
|
40
|
+
{ name: "bot-support", apiKey: "api-key-support" },
|
|
41
|
+
{ name: "bot-marketing", apiKey: "api-key-marketing" },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await provider.initAll(provider.globalVendorArgs.port);
|
|
46
|
+
|
|
47
|
+
// Send message to a specific instance
|
|
48
|
+
await provider.sendText(
|
|
49
|
+
{ from: "573123456789", text: "Hello from sales!" },
|
|
50
|
+
{ instance: "bot-sales" }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Notify admin on another instance
|
|
54
|
+
await provider.sendText(
|
|
55
|
+
{ from: "573199999999", text: "Nuevo pedido recibido" },
|
|
56
|
+
{ instance: "bot-admin" }
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Or use the default (first instance)
|
|
60
|
+
await provider.sendText({
|
|
61
|
+
from: "573123456789",
|
|
62
|
+
text: "Hello!",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// List registered instances
|
|
66
|
+
console.log(provider.getInstances().map((i) => i.name));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Webhook routing:** the provider reads `instanceName` from `body.instanceName`, `body.inputs.instanceName`, or `body.instance.name`. Point all your Sendwave webhooks to the same `/webhook` URL.
|
|
70
|
+
|
|
71
|
+
**QR / State per instance:**
|
|
72
|
+
- `/qr` → default instance
|
|
73
|
+
- `/qr/bot-sales` → specific instance
|
|
74
|
+
- `/state` → default instance
|
|
75
|
+
- `/state/bot-sales` → specific instance
|
|
76
|
+
|
|
77
|
+
## ⚙️ Configuration
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const provider = createSendWaveProvider({
|
|
81
|
+
name: "MyBot",
|
|
82
|
+
apiKey: "your-api-key-here",
|
|
83
|
+
url: "https://msg.sendwaves.cloud", // Optional
|
|
84
|
+
port: 3000,
|
|
85
|
+
host: "0.0.0.0",
|
|
86
|
+
readMessages: true, // Auto-mark incoming messages as read
|
|
87
|
+
message: {
|
|
88
|
+
mergeMessage: true, // Merge rapid messages from same user
|
|
89
|
+
timeMergeMessage: 3, // Base wait time in seconds
|
|
90
|
+
maxWaitTime: 30, // Maximum wait time in seconds
|
|
91
|
+
shortMessageThreshold: 10, // Short messages merge faster
|
|
92
|
+
adaptiveTimer: true, // Smart timing based on message patterns
|
|
93
|
+
},
|
|
94
|
+
queueFlow: {
|
|
95
|
+
enabled: true,
|
|
96
|
+
warningTimeout: 30 * 60 * 1000, // 30 minutes
|
|
97
|
+
endTimeout: 2 * 60 * 1000, // 2 minutes after warning
|
|
98
|
+
},
|
|
99
|
+
payloadLimits: {
|
|
100
|
+
json: "50mb",
|
|
101
|
+
urlencoded: "50mb",
|
|
102
|
+
text: "10mb",
|
|
103
|
+
media: {
|
|
104
|
+
image: "10mb",
|
|
105
|
+
video: "100mb",
|
|
106
|
+
audio: "50mb",
|
|
107
|
+
document: "25mb",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
28
113
|
## 📤 Sending Messages
|
|
29
114
|
|
|
30
115
|
### Text Messages
|
|
@@ -33,31 +118,75 @@ await provider.initAll(provider.globalVendorArgs.port);
|
|
|
33
118
|
await provider.sendText({
|
|
34
119
|
from: "573123456789",
|
|
35
120
|
text: "Hello! Welcome to our service.",
|
|
121
|
+
delay: 2000, // Optional typing delay
|
|
36
122
|
});
|
|
123
|
+
|
|
124
|
+
// Send to a specific instance (multi-tenant)
|
|
125
|
+
await provider.sendText(
|
|
126
|
+
{ from: "573123456789", text: "Hello!" },
|
|
127
|
+
{ instance: "bot-sales" }
|
|
128
|
+
);
|
|
37
129
|
```
|
|
38
130
|
|
|
39
|
-
###
|
|
131
|
+
### Presence Indicators
|
|
40
132
|
|
|
41
133
|
```typescript
|
|
42
|
-
await provider.
|
|
134
|
+
await provider.sendPresence({
|
|
43
135
|
from: "573123456789",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
values: ["Excelente", "Bueno", "Mejorable"],
|
|
47
|
-
selectableCount: 1 // Optional, default: 1
|
|
48
|
-
}
|
|
136
|
+
presence: "composing", // or "recording"
|
|
137
|
+
delay: 2000,
|
|
49
138
|
});
|
|
50
139
|
```
|
|
51
140
|
|
|
52
|
-
###
|
|
141
|
+
### Media Messages
|
|
53
142
|
|
|
54
|
-
|
|
143
|
+
```typescript
|
|
144
|
+
// Image
|
|
145
|
+
await provider.sendImage({
|
|
146
|
+
from: "573123456789",
|
|
147
|
+
url: "https://example.com/image.jpg",
|
|
148
|
+
text: "Optional caption",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Video
|
|
152
|
+
await provider.sendVideo({
|
|
153
|
+
from: "573123456789",
|
|
154
|
+
url: "https://example.com/video.mp4",
|
|
155
|
+
text: "Optional caption",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Document/File
|
|
159
|
+
await provider.sendFile({
|
|
160
|
+
from: "573123456789",
|
|
161
|
+
url: "https://example.com/file.pdf",
|
|
162
|
+
fileName: "document.pdf",
|
|
163
|
+
text: "Optional caption",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Audio
|
|
167
|
+
await provider.sendAudio({
|
|
168
|
+
from: "573123456789",
|
|
169
|
+
url: "https://example.com/audio.mp3",
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Voice message (PTT)
|
|
173
|
+
await provider.sendVoice({
|
|
174
|
+
from: "573123456789",
|
|
175
|
+
url: "https://example.com/voice.ogg",
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Interactive Buttons
|
|
180
|
+
|
|
181
|
+
The provider accepts multiple formats for buttons.
|
|
55
182
|
|
|
56
183
|
```typescript
|
|
57
184
|
// 1. Super Simple (Quick Replies)
|
|
58
185
|
await provider.sendButton({
|
|
59
186
|
from: "573123456789",
|
|
60
187
|
title: "Main Menu",
|
|
188
|
+
body: "Select an option",
|
|
189
|
+
footer: "Sendwaves Provider",
|
|
61
190
|
buttons: ["🛒 Catalog", "🎁 Promotions", "📞 Support"]
|
|
62
191
|
});
|
|
63
192
|
|
|
@@ -69,6 +198,7 @@ await provider.sendButton({
|
|
|
69
198
|
buttons: [
|
|
70
199
|
{ type: "reply", displayText: "Cash" },
|
|
71
200
|
{ type: "url", displayText: "Visit Web", url: "https://example.com" },
|
|
201
|
+
{ type: "call", displayText: "Call Us", phoneNumber: "+573123456789" },
|
|
72
202
|
{ type: "copy", displayText: "Copy Coupon", copyText: "SAVE20" },
|
|
73
203
|
{ type: "pix", displayText: "Pay with Pix", keyType: "email", key: "pix@bot.com" }
|
|
74
204
|
]
|
|
@@ -82,7 +212,40 @@ await provider.sendButton({
|
|
|
82
212
|
- `copy`: Copies text or code to clipboard.
|
|
83
213
|
- `pix`: Brazilian payment system integration.
|
|
84
214
|
|
|
85
|
-
###
|
|
215
|
+
### Carousel Messages
|
|
216
|
+
|
|
217
|
+
Send a scrollable carousel with multiple cards. Each card supports its own buttons.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
await provider.sendCarousel({
|
|
221
|
+
from: "573123456789",
|
|
222
|
+
title: "Featured Products",
|
|
223
|
+
description: "Swipe to see our best offers",
|
|
224
|
+
footer: "Sendwaves Store",
|
|
225
|
+
cards: [
|
|
226
|
+
{
|
|
227
|
+
text: "Premium Plan",
|
|
228
|
+
thumbnailUrl: "https://example.com/plan1.jpg",
|
|
229
|
+
footerText: "Best value",
|
|
230
|
+
buttons: [
|
|
231
|
+
{ type: "reply", displayText: "Buy Now" },
|
|
232
|
+
{ type: "url", displayText: "More Info", url: "https://example.com/plan1" }
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
text: "Basic Plan",
|
|
237
|
+
thumbnailUrl: "https://example.com/plan2.jpg",
|
|
238
|
+
footerText: "Affordable",
|
|
239
|
+
buttons: [
|
|
240
|
+
{ type: "reply", displayText: "Buy Now" }
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
delay: 2000
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Interactive Lists
|
|
86
249
|
|
|
87
250
|
```typescript
|
|
88
251
|
// Simple List (Array of strings)
|
|
@@ -100,7 +263,9 @@ await provider.sendList({
|
|
|
100
263
|
from: "573123456789",
|
|
101
264
|
list: {
|
|
102
265
|
title: "Product Categories",
|
|
266
|
+
description: "Choose a category",
|
|
103
267
|
button: "Select Category",
|
|
268
|
+
footerText: "Sendwaves Provider",
|
|
104
269
|
content: [
|
|
105
270
|
{
|
|
106
271
|
title: "Electronics",
|
|
@@ -114,24 +279,79 @@ await provider.sendList({
|
|
|
114
279
|
});
|
|
115
280
|
```
|
|
116
281
|
|
|
117
|
-
###
|
|
282
|
+
### Polls
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
await provider.sendPoll({
|
|
286
|
+
from: "573123456789",
|
|
287
|
+
poll: {
|
|
288
|
+
name: "¿Qué te parece el nuevo proveedor?",
|
|
289
|
+
values: ["Excelente", "Bueno", "Mejorable"],
|
|
290
|
+
selectableCount: 1 // Optional, default: 1
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Locations
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
await provider.sendLocation({
|
|
299
|
+
from: "573123456789",
|
|
300
|
+
name: "Our Office",
|
|
301
|
+
address: "123 Main St, Bogotá",
|
|
302
|
+
latitude: 4.711,
|
|
303
|
+
longitude: -74.0721,
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Reactions
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
await provider.sendReaction({
|
|
311
|
+
key: {
|
|
312
|
+
remoteJid: "573123456789@s.whatsapp.net",
|
|
313
|
+
fromMe: false,
|
|
314
|
+
id: "MESSAGE_ID",
|
|
315
|
+
},
|
|
316
|
+
reaction: "👍",
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Mark as Read
|
|
118
321
|
|
|
119
322
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
await provider.sendVoice({ from: "573123456789", url: "https://..." });
|
|
323
|
+
await provider.readMessages({
|
|
324
|
+
from: "573123456789@s.whatsapp.net",
|
|
325
|
+
messageId: "MESSAGE_ID",
|
|
326
|
+
});
|
|
125
327
|
```
|
|
126
328
|
|
|
127
|
-
##
|
|
329
|
+
## 🧠 Intelligent Message Merging
|
|
128
330
|
|
|
129
|
-
|
|
331
|
+
When `mergeMessage` is enabled, rapid messages from the same user are merged into a single message.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const provider = createSendWaveProvider({
|
|
335
|
+
message: {
|
|
336
|
+
mergeMessage: true,
|
|
337
|
+
timeMergeMessage: 4,
|
|
338
|
+
maxWaitTime: 30,
|
|
339
|
+
shortMessageThreshold: 10,
|
|
340
|
+
adaptiveTimer: true,
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Example:
|
|
346
|
+
- User sends "Hola" then "como estas"
|
|
347
|
+
- Result: single message with body `"Hola como estas"`
|
|
348
|
+
|
|
349
|
+
## 🔄 Queue Flow System
|
|
350
|
+
|
|
351
|
+
Automatic session management for inactive users.
|
|
130
352
|
|
|
131
353
|
```typescript
|
|
132
354
|
const provider = createSendWaveProvider({
|
|
133
|
-
name: "MyBot",
|
|
134
|
-
apiKey: "your-key",
|
|
135
355
|
queueFlow: {
|
|
136
356
|
enabled: true,
|
|
137
357
|
warningTimeout: 30 * 60 * 1000, // 30 mins
|
|
@@ -139,20 +359,42 @@ const provider = createSendWaveProvider({
|
|
|
139
359
|
warningMessage: "⏳ You seem inactive. Still there?",
|
|
140
360
|
},
|
|
141
361
|
});
|
|
362
|
+
|
|
363
|
+
// Listen to inactivity events
|
|
364
|
+
provider.onQueueFlowEvent("userInactive-573123456789", (data) => {
|
|
365
|
+
console.log(data.isActive ? "User warned" : "Session ended");
|
|
366
|
+
});
|
|
142
367
|
```
|
|
143
368
|
|
|
144
369
|
## 🎯 Event System
|
|
145
370
|
|
|
146
371
|
```typescript
|
|
147
|
-
provider.on(
|
|
148
|
-
provider.on(
|
|
372
|
+
provider.on("ready", (isReady) => console.log("Connected:", isReady));
|
|
373
|
+
provider.on("message", (ctx) => console.log(`From ${ctx.from}: ${ctx.body}`));
|
|
374
|
+
provider.on("user-message", (data) => console.log("User message:", data));
|
|
375
|
+
provider.on("flow-message", (data) => console.log("Flow message:", data));
|
|
376
|
+
provider.on("auth_failure", (payload) => console.error("Auth failed:", payload));
|
|
377
|
+
provider.on("notice", ({ title, instructions }) => console.log(title, instructions));
|
|
149
378
|
```
|
|
150
379
|
|
|
151
380
|
## 🏗️ Architecture
|
|
152
381
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
-
|
|
382
|
+
The project is organized in modular layers:
|
|
383
|
+
|
|
384
|
+
- **`config/`**: Default values and constants.
|
|
385
|
+
- **`domain/`**: Pure types and interfaces.
|
|
386
|
+
- **`infrastructure/`**: HTTP client, API repository, media detection, message parser.
|
|
387
|
+
- **`application/`**: Business logic: message strategies, buffer merging, connection polling, queue flow.
|
|
388
|
+
- **`presentation/`**: Provider and core classes, HTML pages.
|
|
389
|
+
- **`shared/`**: Common utilities and exceptions.
|
|
390
|
+
|
|
391
|
+
### Adding a New Message Type
|
|
392
|
+
|
|
393
|
+
1. Create a strategy in `src/application/messaging/strategies/`.
|
|
394
|
+
2. Implement the `SendStrategy<T>` interface.
|
|
395
|
+
3. Register it in `MessageSenderService`.
|
|
396
|
+
4. Add the public method to `SendWaveProvider`.
|
|
397
|
+
5. Add the type to `ProviderInterface` and `message.types.ts`.
|
|
156
398
|
|
|
157
399
|
## 📜 License
|
|
158
400
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SendWaveApiRepository } from "../../infrastructure/persistence/sendwave-api.repository";
|
|
2
|
+
export interface ConnectionStateListener {
|
|
3
|
+
onConnected: () => void;
|
|
4
|
+
onDisconnected: () => void;
|
|
5
|
+
onAuthFailure: (error: any) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare class ConnectionPollingService {
|
|
8
|
+
private readonly api;
|
|
9
|
+
private readonly listener;
|
|
10
|
+
private timer?;
|
|
11
|
+
private lastReadyState?;
|
|
12
|
+
constructor(api: SendWaveApiRepository, listener: ConnectionStateListener);
|
|
13
|
+
check(): Promise<void>;
|
|
14
|
+
start(): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectionPollingService = void 0;
|
|
4
|
+
const constants_1 = require("../../config/constants");
|
|
5
|
+
class ConnectionPollingService {
|
|
6
|
+
constructor(api, listener) {
|
|
7
|
+
this.api = api;
|
|
8
|
+
this.listener = listener;
|
|
9
|
+
}
|
|
10
|
+
async check() {
|
|
11
|
+
var _a;
|
|
12
|
+
try {
|
|
13
|
+
const res = await this.api.getConnectionState();
|
|
14
|
+
const data = res.data;
|
|
15
|
+
if (((_a = data === null || data === void 0 ? void 0 : data.instance) === null || _a === void 0 ? void 0 : _a.state) === "open") {
|
|
16
|
+
if (this.lastReadyState !== true) {
|
|
17
|
+
this.listener.onConnected();
|
|
18
|
+
this.lastReadyState = true;
|
|
19
|
+
}
|
|
20
|
+
this.stop();
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
if (this.lastReadyState !== false) {
|
|
24
|
+
this.listener.onDisconnected();
|
|
25
|
+
this.lastReadyState = false;
|
|
26
|
+
}
|
|
27
|
+
this.start();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.listener.onAuthFailure(error);
|
|
32
|
+
this.lastReadyState = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
start() {
|
|
36
|
+
if (this.timer)
|
|
37
|
+
return;
|
|
38
|
+
this.timer = setInterval(() => {
|
|
39
|
+
this.check();
|
|
40
|
+
}, constants_1.CONNECTION_POLL_INTERVAL_MS);
|
|
41
|
+
}
|
|
42
|
+
stop() {
|
|
43
|
+
if (this.timer) {
|
|
44
|
+
clearInterval(this.timer);
|
|
45
|
+
this.timer = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.ConnectionPollingService = ConnectionPollingService;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { ParsedMessage } from "../../domain/interfaces";
|
|
3
|
+
import { MessageMergeConfig } from "../../domain/types";
|
|
4
|
+
export declare class MessageBufferService extends EventEmitter {
|
|
5
|
+
private readonly config;
|
|
6
|
+
private buffers;
|
|
7
|
+
constructor(config: MessageMergeConfig);
|
|
8
|
+
buffer(data: ParsedMessage, enqueue: (task: () => Promise<void>) => void): void;
|
|
9
|
+
private calculateTimeout;
|
|
10
|
+
flush(user: string, enqueue: (task: () => Promise<void>) => void): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageBufferService = void 0;
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
class MessageBufferService extends node_events_1.EventEmitter {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super();
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.buffers = {};
|
|
10
|
+
}
|
|
11
|
+
buffer(data, enqueue) {
|
|
12
|
+
const user = data.from;
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
if (!this.buffers[user]) {
|
|
15
|
+
this.buffers[user] = {
|
|
16
|
+
messages: [],
|
|
17
|
+
messageTimeout: null,
|
|
18
|
+
maxWaitTimeout: null,
|
|
19
|
+
lastData: null,
|
|
20
|
+
lastMessageTime: now,
|
|
21
|
+
messageCount: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const buffer = this.buffers[user];
|
|
25
|
+
buffer.messages.push(data.body);
|
|
26
|
+
buffer.lastData = data;
|
|
27
|
+
buffer.messageCount++;
|
|
28
|
+
const timeBetweenMessages = now - buffer.lastMessageTime;
|
|
29
|
+
buffer.lastMessageTime = now;
|
|
30
|
+
if (buffer.messageTimeout) {
|
|
31
|
+
clearTimeout(buffer.messageTimeout);
|
|
32
|
+
buffer.messageTimeout = null;
|
|
33
|
+
}
|
|
34
|
+
if (buffer.messages.length === 1) {
|
|
35
|
+
const maxWaitTime = this.config.maxWaitTime || 30;
|
|
36
|
+
buffer.maxWaitTimeout = setTimeout(() => {
|
|
37
|
+
this.flush(user, enqueue);
|
|
38
|
+
}, maxWaitTime * 1000);
|
|
39
|
+
}
|
|
40
|
+
const timeout = this.calculateTimeout(data.body, timeBetweenMessages, buffer.messageCount);
|
|
41
|
+
buffer.messageTimeout = setTimeout(() => {
|
|
42
|
+
this.flush(user, enqueue);
|
|
43
|
+
}, timeout);
|
|
44
|
+
}
|
|
45
|
+
calculateTimeout(messageText, timeBetweenMessages, messageCount) {
|
|
46
|
+
var _a;
|
|
47
|
+
const baseTimeout = (this.config.timeMergeMessage || 3) * 1000;
|
|
48
|
+
const shortThreshold = this.config.shortMessageThreshold || 10;
|
|
49
|
+
const adaptiveTimer = (_a = this.config.adaptiveTimer) !== null && _a !== void 0 ? _a : true;
|
|
50
|
+
let timeout = baseTimeout;
|
|
51
|
+
if (messageText.length < shortThreshold) {
|
|
52
|
+
timeout = Math.max(1000, timeout * 0.5);
|
|
53
|
+
}
|
|
54
|
+
if (adaptiveTimer) {
|
|
55
|
+
if (timeBetweenMessages < 2000 && messageCount > 1) {
|
|
56
|
+
timeout = Math.max(timeout, 4000);
|
|
57
|
+
}
|
|
58
|
+
if (messageCount <= 2) {
|
|
59
|
+
timeout = Math.max(2000, timeout * 0.7);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return timeout;
|
|
63
|
+
}
|
|
64
|
+
flush(user, enqueue) {
|
|
65
|
+
const buffer = this.buffers[user];
|
|
66
|
+
if (!buffer || buffer.messages.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
if (buffer.messageTimeout)
|
|
69
|
+
clearTimeout(buffer.messageTimeout);
|
|
70
|
+
if (buffer.maxWaitTimeout)
|
|
71
|
+
clearTimeout(buffer.maxWaitTimeout);
|
|
72
|
+
const fullMessage = buffer.messages.join(" ");
|
|
73
|
+
enqueue(async () => {
|
|
74
|
+
this.emit("message", { ...buffer.lastData, body: fullMessage });
|
|
75
|
+
});
|
|
76
|
+
delete this.buffers[user];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.MessageBufferService = MessageBufferService;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SendWaveApiRepository } from "../../infrastructure/persistence/sendwave-api.repository";
|
|
2
|
+
import { ReadMessage, SendButton, SendCarousel, SendList, SendLocation, SendMedia, SendMessage, SendPoll, SendPresence, SendReaction } from "../../domain/types";
|
|
3
|
+
import { ProviderInterface } from "../../domain/interfaces";
|
|
4
|
+
export declare class MessageSenderService implements ProviderInterface {
|
|
5
|
+
private readonly api;
|
|
6
|
+
private readonly text;
|
|
7
|
+
private readonly presence;
|
|
8
|
+
private readonly image;
|
|
9
|
+
private readonly video;
|
|
10
|
+
private readonly audio;
|
|
11
|
+
private readonly file;
|
|
12
|
+
private readonly voice;
|
|
13
|
+
private readonly list;
|
|
14
|
+
private readonly button;
|
|
15
|
+
private readonly carousel;
|
|
16
|
+
private readonly poll;
|
|
17
|
+
private readonly location;
|
|
18
|
+
private readonly reaction;
|
|
19
|
+
private readonly readMessagesStrategy;
|
|
20
|
+
constructor(api: SendWaveApiRepository);
|
|
21
|
+
dispatch(from: string, text: any, options?: any): Promise<any>;
|
|
22
|
+
sendMediaByType(data: SendMedia & {
|
|
23
|
+
text?: string;
|
|
24
|
+
}): Promise<any>;
|
|
25
|
+
sendText(data: SendMessage): Promise<any>;
|
|
26
|
+
sendPresence(data: SendPresence): Promise<any>;
|
|
27
|
+
sendImage(data: SendMedia): Promise<any>;
|
|
28
|
+
sendVideo(data: SendMedia): Promise<any>;
|
|
29
|
+
sendAudio(data: SendMedia): Promise<any>;
|
|
30
|
+
sendFile(data: SendMedia): Promise<any>;
|
|
31
|
+
sendVoice(data: SendMedia): Promise<any>;
|
|
32
|
+
sendList(data: SendList): Promise<any>;
|
|
33
|
+
sendButton(data: SendButton): Promise<any>;
|
|
34
|
+
sendCarousel(data: SendCarousel): Promise<any>;
|
|
35
|
+
sendPoll(data: SendPoll): Promise<any>;
|
|
36
|
+
sendLocation(data: SendLocation): Promise<any>;
|
|
37
|
+
sendReaction(data: SendReaction): Promise<any>;
|
|
38
|
+
sendMedia(data: SendMedia): Promise<any>;
|
|
39
|
+
readMessages(data: ReadMessage): Promise<any>;
|
|
40
|
+
}
|