@gamastudio/sendwave-provider 1.0.3 → 1.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/.github/workflows/build.yml +31 -31
- package/README.md +261 -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 +73 -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,85 @@ 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
|
+
"bot-sales"
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Or use the default (first instance)
|
|
54
|
+
await provider.sendText({
|
|
55
|
+
from: "573123456789",
|
|
56
|
+
text: "Hello!",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// List registered instances
|
|
60
|
+
console.log(provider.getInstances().map((i) => i.name));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**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.
|
|
64
|
+
|
|
65
|
+
**QR / State per instance:**
|
|
66
|
+
- `/qr` → default instance
|
|
67
|
+
- `/qr/bot-sales` → specific instance
|
|
68
|
+
- `/state` → default instance
|
|
69
|
+
- `/state/bot-sales` → specific instance
|
|
70
|
+
|
|
71
|
+
## ⚙️ Configuration
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const provider = createSendWaveProvider({
|
|
75
|
+
name: "MyBot",
|
|
76
|
+
apiKey: "your-api-key-here",
|
|
77
|
+
url: "https://msg.sendwaves.cloud", // Optional
|
|
78
|
+
port: 3000,
|
|
79
|
+
host: "0.0.0.0",
|
|
80
|
+
readMessages: true, // Auto-mark incoming messages as read
|
|
81
|
+
message: {
|
|
82
|
+
mergeMessage: true, // Merge rapid messages from same user
|
|
83
|
+
timeMergeMessage: 3, // Base wait time in seconds
|
|
84
|
+
maxWaitTime: 30, // Maximum wait time in seconds
|
|
85
|
+
shortMessageThreshold: 10, // Short messages merge faster
|
|
86
|
+
adaptiveTimer: true, // Smart timing based on message patterns
|
|
87
|
+
},
|
|
88
|
+
queueFlow: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
warningTimeout: 30 * 60 * 1000, // 30 minutes
|
|
91
|
+
endTimeout: 2 * 60 * 1000, // 2 minutes after warning
|
|
92
|
+
},
|
|
93
|
+
payloadLimits: {
|
|
94
|
+
json: "50mb",
|
|
95
|
+
urlencoded: "50mb",
|
|
96
|
+
text: "10mb",
|
|
97
|
+
media: {
|
|
98
|
+
image: "10mb",
|
|
99
|
+
video: "100mb",
|
|
100
|
+
audio: "50mb",
|
|
101
|
+
document: "25mb",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
28
107
|
## 📤 Sending Messages
|
|
29
108
|
|
|
30
109
|
### Text Messages
|
|
@@ -33,31 +112,75 @@ await provider.initAll(provider.globalVendorArgs.port);
|
|
|
33
112
|
await provider.sendText({
|
|
34
113
|
from: "573123456789",
|
|
35
114
|
text: "Hello! Welcome to our service.",
|
|
115
|
+
delay: 2000, // Optional typing delay
|
|
36
116
|
});
|
|
117
|
+
|
|
118
|
+
// Send to a specific instance (multi-tenant)
|
|
119
|
+
await provider.sendText(
|
|
120
|
+
{ from: "573123456789", text: "Hello!" },
|
|
121
|
+
"bot-sales"
|
|
122
|
+
);
|
|
37
123
|
```
|
|
38
124
|
|
|
39
|
-
###
|
|
125
|
+
### Presence Indicators
|
|
40
126
|
|
|
41
127
|
```typescript
|
|
42
|
-
await provider.
|
|
128
|
+
await provider.sendPresence({
|
|
43
129
|
from: "573123456789",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
130
|
+
presence: "composing", // or "recording"
|
|
131
|
+
delay: 2000,
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Media Messages
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Image
|
|
139
|
+
await provider.sendImage({
|
|
140
|
+
from: "573123456789",
|
|
141
|
+
url: "https://example.com/image.jpg",
|
|
142
|
+
text: "Optional caption",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Video
|
|
146
|
+
await provider.sendVideo({
|
|
147
|
+
from: "573123456789",
|
|
148
|
+
url: "https://example.com/video.mp4",
|
|
149
|
+
text: "Optional caption",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Document/File
|
|
153
|
+
await provider.sendFile({
|
|
154
|
+
from: "573123456789",
|
|
155
|
+
url: "https://example.com/file.pdf",
|
|
156
|
+
fileName: "document.pdf",
|
|
157
|
+
text: "Optional caption",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Audio
|
|
161
|
+
await provider.sendAudio({
|
|
162
|
+
from: "573123456789",
|
|
163
|
+
url: "https://example.com/audio.mp3",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Voice message (PTT)
|
|
167
|
+
await provider.sendVoice({
|
|
168
|
+
from: "573123456789",
|
|
169
|
+
url: "https://example.com/voice.ogg",
|
|
49
170
|
});
|
|
50
171
|
```
|
|
51
172
|
|
|
52
|
-
###
|
|
173
|
+
### Interactive Buttons
|
|
53
174
|
|
|
54
|
-
The provider
|
|
175
|
+
The provider accepts multiple formats for buttons.
|
|
55
176
|
|
|
56
177
|
```typescript
|
|
57
178
|
// 1. Super Simple (Quick Replies)
|
|
58
179
|
await provider.sendButton({
|
|
59
180
|
from: "573123456789",
|
|
60
181
|
title: "Main Menu",
|
|
182
|
+
body: "Select an option",
|
|
183
|
+
footer: "Sendwaves Provider",
|
|
61
184
|
buttons: ["🛒 Catalog", "🎁 Promotions", "📞 Support"]
|
|
62
185
|
});
|
|
63
186
|
|
|
@@ -69,6 +192,7 @@ await provider.sendButton({
|
|
|
69
192
|
buttons: [
|
|
70
193
|
{ type: "reply", displayText: "Cash" },
|
|
71
194
|
{ type: "url", displayText: "Visit Web", url: "https://example.com" },
|
|
195
|
+
{ type: "call", displayText: "Call Us", phoneNumber: "+573123456789" },
|
|
72
196
|
{ type: "copy", displayText: "Copy Coupon", copyText: "SAVE20" },
|
|
73
197
|
{ type: "pix", displayText: "Pay with Pix", keyType: "email", key: "pix@bot.com" }
|
|
74
198
|
]
|
|
@@ -82,7 +206,40 @@ await provider.sendButton({
|
|
|
82
206
|
- `copy`: Copies text or code to clipboard.
|
|
83
207
|
- `pix`: Brazilian payment system integration.
|
|
84
208
|
|
|
85
|
-
###
|
|
209
|
+
### Carousel Messages
|
|
210
|
+
|
|
211
|
+
Send a scrollable carousel with multiple cards. Each card supports its own buttons.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
await provider.sendCarousel({
|
|
215
|
+
from: "573123456789",
|
|
216
|
+
title: "Featured Products",
|
|
217
|
+
description: "Swipe to see our best offers",
|
|
218
|
+
footer: "Sendwaves Store",
|
|
219
|
+
cards: [
|
|
220
|
+
{
|
|
221
|
+
text: "Premium Plan",
|
|
222
|
+
thumbnailUrl: "https://example.com/plan1.jpg",
|
|
223
|
+
footerText: "Best value",
|
|
224
|
+
buttons: [
|
|
225
|
+
{ type: "reply", displayText: "Buy Now" },
|
|
226
|
+
{ type: "url", displayText: "More Info", url: "https://example.com/plan1" }
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
text: "Basic Plan",
|
|
231
|
+
thumbnailUrl: "https://example.com/plan2.jpg",
|
|
232
|
+
footerText: "Affordable",
|
|
233
|
+
buttons: [
|
|
234
|
+
{ type: "reply", displayText: "Buy Now" }
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
delay: 2000
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Interactive Lists
|
|
86
243
|
|
|
87
244
|
```typescript
|
|
88
245
|
// Simple List (Array of strings)
|
|
@@ -100,7 +257,9 @@ await provider.sendList({
|
|
|
100
257
|
from: "573123456789",
|
|
101
258
|
list: {
|
|
102
259
|
title: "Product Categories",
|
|
260
|
+
description: "Choose a category",
|
|
103
261
|
button: "Select Category",
|
|
262
|
+
footerText: "Sendwaves Provider",
|
|
104
263
|
content: [
|
|
105
264
|
{
|
|
106
265
|
title: "Electronics",
|
|
@@ -114,24 +273,79 @@ await provider.sendList({
|
|
|
114
273
|
});
|
|
115
274
|
```
|
|
116
275
|
|
|
117
|
-
###
|
|
276
|
+
### Polls
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
await provider.sendPoll({
|
|
280
|
+
from: "573123456789",
|
|
281
|
+
poll: {
|
|
282
|
+
name: "¿Qué te parece el nuevo proveedor?",
|
|
283
|
+
values: ["Excelente", "Bueno", "Mejorable"],
|
|
284
|
+
selectableCount: 1 // Optional, default: 1
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Locations
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
await provider.sendLocation({
|
|
293
|
+
from: "573123456789",
|
|
294
|
+
name: "Our Office",
|
|
295
|
+
address: "123 Main St, Bogotá",
|
|
296
|
+
latitude: 4.711,
|
|
297
|
+
longitude: -74.0721,
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Reactions
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
await provider.sendReaction({
|
|
305
|
+
key: {
|
|
306
|
+
remoteJid: "573123456789@s.whatsapp.net",
|
|
307
|
+
fromMe: false,
|
|
308
|
+
id: "MESSAGE_ID",
|
|
309
|
+
},
|
|
310
|
+
reaction: "👍",
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Mark as Read
|
|
118
315
|
|
|
119
316
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
await provider.sendVoice({ from: "573123456789", url: "https://..." });
|
|
317
|
+
await provider.readMessages({
|
|
318
|
+
from: "573123456789@s.whatsapp.net",
|
|
319
|
+
messageId: "MESSAGE_ID",
|
|
320
|
+
});
|
|
125
321
|
```
|
|
126
322
|
|
|
127
|
-
##
|
|
323
|
+
## 🧠 Intelligent Message Merging
|
|
128
324
|
|
|
129
|
-
|
|
325
|
+
When `mergeMessage` is enabled, rapid messages from the same user are merged into a single message.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const provider = createSendWaveProvider({
|
|
329
|
+
message: {
|
|
330
|
+
mergeMessage: true,
|
|
331
|
+
timeMergeMessage: 4,
|
|
332
|
+
maxWaitTime: 30,
|
|
333
|
+
shortMessageThreshold: 10,
|
|
334
|
+
adaptiveTimer: true,
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Example:
|
|
340
|
+
- User sends "Hola" then "como estas"
|
|
341
|
+
- Result: single message with body `"Hola como estas"`
|
|
342
|
+
|
|
343
|
+
## 🔄 Queue Flow System
|
|
344
|
+
|
|
345
|
+
Automatic session management for inactive users.
|
|
130
346
|
|
|
131
347
|
```typescript
|
|
132
348
|
const provider = createSendWaveProvider({
|
|
133
|
-
name: "MyBot",
|
|
134
|
-
apiKey: "your-key",
|
|
135
349
|
queueFlow: {
|
|
136
350
|
enabled: true,
|
|
137
351
|
warningTimeout: 30 * 60 * 1000, // 30 mins
|
|
@@ -139,20 +353,42 @@ const provider = createSendWaveProvider({
|
|
|
139
353
|
warningMessage: "⏳ You seem inactive. Still there?",
|
|
140
354
|
},
|
|
141
355
|
});
|
|
356
|
+
|
|
357
|
+
// Listen to inactivity events
|
|
358
|
+
provider.onQueueFlowEvent("userInactive-573123456789", (data) => {
|
|
359
|
+
console.log(data.isActive ? "User warned" : "Session ended");
|
|
360
|
+
});
|
|
142
361
|
```
|
|
143
362
|
|
|
144
363
|
## 🎯 Event System
|
|
145
364
|
|
|
146
365
|
```typescript
|
|
147
|
-
provider.on(
|
|
148
|
-
provider.on(
|
|
366
|
+
provider.on("ready", (isReady) => console.log("Connected:", isReady));
|
|
367
|
+
provider.on("message", (ctx) => console.log(`From ${ctx.from}: ${ctx.body}`));
|
|
368
|
+
provider.on("user-message", (data) => console.log("User message:", data));
|
|
369
|
+
provider.on("flow-message", (data) => console.log("Flow message:", data));
|
|
370
|
+
provider.on("auth_failure", (payload) => console.error("Auth failed:", payload));
|
|
371
|
+
provider.on("notice", ({ title, instructions }) => console.log(title, instructions));
|
|
149
372
|
```
|
|
150
373
|
|
|
151
374
|
## 🏗️ Architecture
|
|
152
375
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
-
|
|
376
|
+
The project is organized in modular layers:
|
|
377
|
+
|
|
378
|
+
- **`config/`**: Default values and constants.
|
|
379
|
+
- **`domain/`**: Pure types and interfaces.
|
|
380
|
+
- **`infrastructure/`**: HTTP client, API repository, media detection, message parser.
|
|
381
|
+
- **`application/`**: Business logic: message strategies, buffer merging, connection polling, queue flow.
|
|
382
|
+
- **`presentation/`**: Provider and core classes, HTML pages.
|
|
383
|
+
- **`shared/`**: Common utilities and exceptions.
|
|
384
|
+
|
|
385
|
+
### Adding a New Message Type
|
|
386
|
+
|
|
387
|
+
1. Create a strategy in `src/application/messaging/strategies/`.
|
|
388
|
+
2. Implement the `SendStrategy<T>` interface.
|
|
389
|
+
3. Register it in `MessageSenderService`.
|
|
390
|
+
4. Add the public method to `SendWaveProvider`.
|
|
391
|
+
5. Add the type to `ProviderInterface` and `message.types.ts`.
|
|
156
392
|
|
|
157
393
|
## 📜 License
|
|
158
394
|
|
|
@@ -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
|
+
}
|