@develit-services/notification 0.5.1 → 0.6.1
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/README.md +62 -5
- package/dist/database/schema.cjs +4 -6
- package/dist/database/schema.d.cts +6 -2
- package/dist/database/schema.d.mts +6 -2
- package/dist/database/schema.d.ts +6 -2
- package/dist/database/schema.mjs +5 -3
- package/dist/export/worker.cjs +290 -66
- package/dist/export/worker.d.cts +22 -3
- package/dist/export/worker.d.mts +22 -3
- package/dist/export/worker.d.ts +22 -3
- package/dist/export/worker.mjs +282 -58
- package/dist/export/wrangler.cjs +19 -1
- package/dist/export/wrangler.d.cts +5 -1
- package/dist/export/wrangler.d.mts +5 -1
- package/dist/export/wrangler.d.ts +5 -1
- package/dist/export/wrangler.mjs +19 -1
- package/dist/shared/{notification.B0pktSz9.cjs → notification.BLPB8Ib2.cjs} +79 -0
- package/dist/shared/{notification.DWuoMDHY.d.ts → notification.BiG4Q650.d.cts} +98 -6
- package/dist/shared/{notification.B4ZLWDCP.d.cts → notification.BiG4Q650.d.mts} +98 -6
- package/dist/shared/{notification.CpFoKjoN.d.mts → notification.BiG4Q650.d.ts} +98 -6
- package/dist/shared/{notification.CmITLO7E.mjs → notification.CP_hFlNt.mjs} +75 -1
- package/dist/shared/{notification.BB9Jl8DI.d.mts → notification.CdlaOUd0.d.cts} +3 -0
- package/dist/shared/{notification.BB9Jl8DI.d.ts → notification.CdlaOUd0.d.mts} +3 -0
- package/dist/shared/{notification.BB9Jl8DI.d.cts → notification.CdlaOUd0.d.ts} +3 -0
- package/dist/types.cjs +19 -19
- package/dist/types.d.cts +5 -16
- package/dist/types.d.mts +5 -16
- package/dist/types.d.ts +5 -16
- package/dist/types.mjs +2 -10
- package/package.json +3 -3
- package/dist/shared/notification.4b3eUEIG.cjs +0 -22
- package/dist/shared/notification.BWLPh6Gb.d.cts +0 -140
- package/dist/shared/notification.BWLPh6Gb.d.mts +0 -140
- package/dist/shared/notification.BWLPh6Gb.d.ts +0 -140
- package/dist/shared/notification.C0X8Orrh.mjs +0 -19
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Bindings:
|
|
|
35
35
|
| Email | Ecomail | Aktivni |
|
|
36
36
|
| SMS | Twilio | Aktivni |
|
|
37
37
|
| Slack | Slack Webhook | Aktivni |
|
|
38
|
+
| Webhook | WebhookConnector | Aktivni |
|
|
38
39
|
| Push | - | Neimplementovano |
|
|
39
40
|
|
|
40
41
|
## Queue processing
|
|
@@ -51,10 +52,11 @@ NOTIFICATIONS_QUEUE
|
|
|
51
52
|
│
|
|
52
53
|
▼
|
|
53
54
|
Queue handler (switch dle type)
|
|
54
|
-
├─ email
|
|
55
|
-
├─ sms
|
|
56
|
-
├─ slack
|
|
57
|
-
|
|
55
|
+
├─ email → _sendEmail()
|
|
56
|
+
├─ sms → _sendSms()
|
|
57
|
+
├─ slack → sendSlackNotification()
|
|
58
|
+
├─ webhook → _sendWebhook()
|
|
59
|
+
└─ push → _sendPushNotification() (501)
|
|
58
60
|
│
|
|
59
61
|
├─ success → message.ack()
|
|
60
62
|
└─ error → message.retry() (exponential backoff, base 60s)
|
|
@@ -68,7 +70,7 @@ Queue handler (switch dle type)
|
|
|
68
70
|
|
|
69
71
|
```typescript
|
|
70
72
|
{
|
|
71
|
-
type: 'email' | 'sms' | 'pushNotification' | 'slack'
|
|
73
|
+
type: 'email' | 'sms' | 'pushNotification' | 'slack' | 'webhook'
|
|
72
74
|
metadata: {
|
|
73
75
|
userAgent?: string
|
|
74
76
|
ip?: string
|
|
@@ -79,6 +81,7 @@ Queue handler (switch dle type)
|
|
|
79
81
|
sms?: ISms
|
|
80
82
|
pushNotification?: IPushNotification
|
|
81
83
|
slack?: ISlack
|
|
84
|
+
webhook?: IWebhook
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
```
|
|
@@ -103,6 +106,53 @@ Provider pro SMS pres Twilio Messaging Service.
|
|
|
103
106
|
|
|
104
107
|
Odesilani notifikaci pres Slack Incoming Webhook. Timeout 3s.
|
|
105
108
|
|
|
109
|
+
### Webhook (HTTP callback)
|
|
110
|
+
|
|
111
|
+
Odesilani podepsanych HTTP POST callbacku na URL zadanou callerem. Timeout 10s.
|
|
112
|
+
|
|
113
|
+
Kazdy webhook je automaticky podepsan RSA-PKCS1-v1_5 (SHA-256) podpisem. Privatni klic se nacita ze Secrets Store (`NOTIFICATION_SERVICE_WEBHOOK_SIGNING_KEY`). Podpis se odesila v hlavicce `X-Webhook-Signature` (base64).
|
|
114
|
+
|
|
115
|
+
#### Generovani klicu
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Private key (base64, jedna radka) → ulozit do Secrets Store jako NOTIFICATION_SERVICE_WEBHOOK_SIGNING_KEY
|
|
119
|
+
openssl genrsa 4096 2>/dev/null | base64 -w0
|
|
120
|
+
|
|
121
|
+
# Public key z private key → poskytnout prijemcum webhooku v API docs
|
|
122
|
+
echo "<PRIVATE_KEY_BASE64>" | base64 -d | openssl rsa -pubout 2>/dev/null | base64 -w0
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> Na macOS pouzit `base64` bez `-w0` (macOS verze nevypisuje newlines).
|
|
126
|
+
|
|
127
|
+
#### Overeni podpisu na strane prijemce
|
|
128
|
+
|
|
129
|
+
Podpis je `RSA-PKCS1-v1_5` se `SHA-256`, odeslany v hlavicce `X-Webhook-Signature` jako base64 string. Overeni:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Ulozit raw body requestu do souboru
|
|
133
|
+
echo -n '{"type":"payment.created","data":{...}}' > body.json
|
|
134
|
+
|
|
135
|
+
# Dekodovat podpis z hlavicky
|
|
136
|
+
echo -n "<hodnota X-Webhook-Signature>" | base64 -d > signature.bin
|
|
137
|
+
|
|
138
|
+
# Overit
|
|
139
|
+
openssl dgst -sha256 -verify webhook_public.pem -signature signature.bin body.json
|
|
140
|
+
# Vystup: "Verified OK" nebo "Verification Failure"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Nebo programove (Node.js):
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
const crypto = require('crypto')
|
|
147
|
+
|
|
148
|
+
function verifyWebhook(rawBody, signatureBase64, publicKeyBase64) {
|
|
149
|
+
const publicKey = Buffer.from(publicKeyBase64, 'base64').toString('utf-8')
|
|
150
|
+
const verifier = crypto.createVerify('RSA-SHA256')
|
|
151
|
+
verifier.update(rawBody)
|
|
152
|
+
return verifier.verify(publicKey, signatureBase64, 'base64')
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
106
156
|
## RPC akce
|
|
107
157
|
|
|
108
158
|
Notification service je **RPC worker** - vsechny akce dostupne pres Cloudflare Worker binding.
|
|
@@ -115,6 +165,7 @@ Notification service je **RPC worker** - vsechny akce dostupne pres Cloudflare W
|
|
|
115
165
|
| `public-send-email-sync` | Odesle email primo (sync) |
|
|
116
166
|
| `public-send-sms` | Zaradi SMS do fronty (async) |
|
|
117
167
|
| `send-slack-notification` | Odesle Slack notifikaci (sync) |
|
|
168
|
+
| `send-webhook` | Zaradi webhook do fronty (async) |
|
|
118
169
|
|
|
119
170
|
### Interni akce
|
|
120
171
|
|
|
@@ -122,6 +173,7 @@ Notification service je **RPC worker** - vsechny akce dostupne pres Cloudflare W
|
|
|
122
173
|
|------|-------|
|
|
123
174
|
| `private-send-email` | Odesle email pres konektor + audit log |
|
|
124
175
|
| `private-send-sms` | Odesle SMS pres konektor + audit log |
|
|
176
|
+
| `private-send-webhook` | Odesle webhook pres konektor + audit log |
|
|
125
177
|
| `send-push-notification` | Neimplementovano (501) |
|
|
126
178
|
|
|
127
179
|
### Vstupy
|
|
@@ -145,6 +197,8 @@ metadata: {
|
|
|
145
197
|
|
|
146
198
|
**Slack** podporuje: `message`.
|
|
147
199
|
|
|
200
|
+
**Webhook** podporuje: `url`, `payload` (libovolny JSON objekt), `headers` (volitelne custom HTTP headers).
|
|
201
|
+
|
|
148
202
|
## Audit logging
|
|
149
203
|
|
|
150
204
|
Kazdy uspesne odeslany email a SMS se zaznamenava do audit logu.
|
|
@@ -177,6 +231,9 @@ Format: `{CATEGORY}-N-{NUMBER}`
|
|
|
177
231
|
| `CONN-N-03` | 502 | Twilio: failed to send SMS |
|
|
178
232
|
| `CONN-N-04` | 502 | Slack: failed to send notification |
|
|
179
233
|
| `CONN-N-05` | 504 | Slack: request timed out |
|
|
234
|
+
| `CONN-N-06` | 502 | Webhook: delivery failed |
|
|
235
|
+
| `CONN-N-07` | 504 | Webhook: request timed out |
|
|
180
236
|
| `VALID-N-01` | 404 | Unsupported email provider |
|
|
181
237
|
| `VALID-N-02` | 404 | Unsupported SMS provider |
|
|
182
238
|
| `SYS-N-01` | 501 | Push notifications not implemented |
|
|
239
|
+
| `SYS-N-02` | 500 | Webhook signing key not configured |
|
package/dist/database/schema.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const schema = {
|
|
4
|
+
__proto__: null
|
|
5
|
+
};
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
exports.auditLog = database_schema.auditLog;
|
|
7
|
+
exports.schema = schema;
|
package/dist/database/schema.mjs
CHANGED
package/dist/export/worker.cjs
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
const backendSdk = require('@develit-io/backend-sdk');
|
|
6
|
-
const database_schema = require('../
|
|
6
|
+
const database_schema = require('../database/schema.cjs');
|
|
7
7
|
require('drizzle-orm');
|
|
8
8
|
require('drizzle-orm/sqlite-core');
|
|
9
|
-
const
|
|
9
|
+
const webhook_connector = require('../shared/notification.BLPB8Ib2.cjs');
|
|
10
|
+
require('@develit-io/backend-sdk/signature');
|
|
10
11
|
const cloudflare_workers = require('cloudflare:workers');
|
|
11
12
|
const d1 = require('drizzle-orm/d1');
|
|
12
13
|
require('zod');
|
|
@@ -16,7 +17,7 @@ require('twilio');
|
|
|
16
17
|
const tables = database_schema.schema;
|
|
17
18
|
|
|
18
19
|
const initiateEmailConnector = async (provider, apiKey, smtpHost, senderEmail, senderName) => {
|
|
19
|
-
const connector = [
|
|
20
|
+
const connector = [webhook_connector.EcomailConnector].find(
|
|
20
21
|
(conn) => conn.providerName === provider
|
|
21
22
|
);
|
|
22
23
|
if (!connector)
|
|
@@ -33,34 +34,12 @@ const initiateEmailConnector = async (provider, apiKey, smtpHost, senderEmail, s
|
|
|
33
34
|
});
|
|
34
35
|
};
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
event,
|
|
40
|
-
description,
|
|
41
|
-
initiatorService,
|
|
42
|
-
initiatorUserId,
|
|
43
|
-
ip,
|
|
44
|
-
userAgent
|
|
45
|
-
}
|
|
46
|
-
}) => {
|
|
47
|
-
const command = db.insert(tables.auditLog).values({
|
|
48
|
-
id: backendSdk.uuidv4(),
|
|
49
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
50
|
-
event,
|
|
51
|
-
description,
|
|
52
|
-
ip,
|
|
53
|
-
userAgent,
|
|
54
|
-
initiatorService,
|
|
55
|
-
initiatorUserId
|
|
56
|
-
});
|
|
57
|
-
return {
|
|
58
|
-
command
|
|
59
|
-
};
|
|
60
|
-
};
|
|
37
|
+
function generateEmailKVPattern(recipient) {
|
|
38
|
+
return `email:${recipient}:`;
|
|
39
|
+
}
|
|
61
40
|
|
|
62
41
|
const initiateSmsConnector = async (provider, accountId, authToken, serviceId) => {
|
|
63
|
-
const connector = [
|
|
42
|
+
const connector = [webhook_connector.TwilioConnector].find(
|
|
64
43
|
(conn) => conn.providerName === provider
|
|
65
44
|
);
|
|
66
45
|
if (!connector)
|
|
@@ -91,7 +70,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
91
70
|
) {
|
|
92
71
|
constructor(ctx, env) {
|
|
93
72
|
super(ctx, env);
|
|
94
|
-
this.slackConnector = new
|
|
73
|
+
this.slackConnector = new webhook_connector.SlackConnector(this.env.SLACK_WEBHOOK);
|
|
95
74
|
this.db = d1.drizzle(this.env.NOTIFICATION_D1, { schema: tables });
|
|
96
75
|
}
|
|
97
76
|
async queue(batch) {
|
|
@@ -121,6 +100,12 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
121
100
|
metadata
|
|
122
101
|
});
|
|
123
102
|
break;
|
|
103
|
+
case "webhook":
|
|
104
|
+
notificationAction = async () => this._sendWebhook({
|
|
105
|
+
webhook: payload.webhook,
|
|
106
|
+
metadata
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
124
109
|
default:
|
|
125
110
|
this.logError({ error: `Unknown notification type: ${type}` });
|
|
126
111
|
message.retry();
|
|
@@ -137,7 +122,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
137
122
|
}
|
|
138
123
|
async _sendEmail(input) {
|
|
139
124
|
return this.handleAction(
|
|
140
|
-
{ data: input, schema:
|
|
125
|
+
{ data: input, schema: webhook_connector.sendEmailInputSchema },
|
|
141
126
|
{ successMessage: "Email sent." },
|
|
142
127
|
async (params) => {
|
|
143
128
|
const {
|
|
@@ -145,7 +130,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
145
130
|
metadata: {
|
|
146
131
|
ip,
|
|
147
132
|
userAgent,
|
|
148
|
-
initiator: {
|
|
133
|
+
initiator: { userId }
|
|
149
134
|
}
|
|
150
135
|
} = params;
|
|
151
136
|
if (!this.emailConnector) {
|
|
@@ -160,26 +145,35 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
160
145
|
);
|
|
161
146
|
}
|
|
162
147
|
await this.emailConnector.sendEmail(email);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
148
|
+
try {
|
|
149
|
+
await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
|
|
150
|
+
logType: "event",
|
|
151
|
+
service: "notification",
|
|
152
|
+
correlationId: backendSdk.uuidv4(),
|
|
153
|
+
eventType: "notification.email_sent",
|
|
154
|
+
actorUserId: userId,
|
|
155
|
+
actorEmail: userId || "SYSTEM",
|
|
156
|
+
targetType: "email",
|
|
157
|
+
targetId: email.to,
|
|
158
|
+
sourceIp: ip,
|
|
171
159
|
userAgent,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
160
|
+
outcome: "success",
|
|
161
|
+
description: `Email sent to ${email.to}`,
|
|
162
|
+
details: JSON.stringify({
|
|
163
|
+
subject: email.subject,
|
|
164
|
+
templateId: email.templateId
|
|
165
|
+
})
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.error("Failed to push email audit log", e);
|
|
169
|
+
}
|
|
176
170
|
return {};
|
|
177
171
|
}
|
|
178
172
|
);
|
|
179
173
|
}
|
|
180
174
|
async _sendSms(input) {
|
|
181
175
|
return this.handleAction(
|
|
182
|
-
{ data: input, schema:
|
|
176
|
+
{ data: input, schema: webhook_connector.sendSmsInputSchema },
|
|
183
177
|
{ successMessage: "Sms sent." },
|
|
184
178
|
async (params) => {
|
|
185
179
|
const {
|
|
@@ -187,7 +181,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
187
181
|
metadata: {
|
|
188
182
|
ip,
|
|
189
183
|
userAgent,
|
|
190
|
-
initiator: {
|
|
184
|
+
initiator: { userId }
|
|
191
185
|
}
|
|
192
186
|
} = params;
|
|
193
187
|
if (!this.smsConnector) {
|
|
@@ -205,26 +199,32 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
205
199
|
);
|
|
206
200
|
}
|
|
207
201
|
await this.smsConnector.sendSms({ message, to });
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
try {
|
|
203
|
+
await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
|
|
204
|
+
logType: "event",
|
|
205
|
+
service: "notification",
|
|
206
|
+
correlationId: backendSdk.uuidv4(),
|
|
207
|
+
eventType: "notification.sms_sent",
|
|
208
|
+
actorUserId: userId,
|
|
209
|
+
actorEmail: userId || "SYSTEM",
|
|
210
|
+
targetType: "sms",
|
|
211
|
+
targetId: to,
|
|
212
|
+
sourceIp: ip,
|
|
214
213
|
userAgent,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
|
|
214
|
+
outcome: "success",
|
|
215
|
+
description: `SMS sent to ${to}`,
|
|
216
|
+
details: JSON.stringify({ message })
|
|
217
|
+
});
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.error("Failed to push sms audit log", e);
|
|
220
|
+
}
|
|
221
221
|
return {};
|
|
222
222
|
}
|
|
223
223
|
);
|
|
224
224
|
}
|
|
225
225
|
async sendEmail(input) {
|
|
226
226
|
return this.handleAction(
|
|
227
|
-
{ data: input, schema:
|
|
227
|
+
{ data: input, schema: webhook_connector.sendEmailInputSchema },
|
|
228
228
|
{ successMessage: "Email sent." },
|
|
229
229
|
async (params) => {
|
|
230
230
|
const { email, metadata } = params;
|
|
@@ -244,7 +244,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
244
244
|
}
|
|
245
245
|
async sendEmailSync(input) {
|
|
246
246
|
return this.handleAction(
|
|
247
|
-
{ data: input, schema:
|
|
247
|
+
{ data: input, schema: webhook_connector.sendEmailInputSchema },
|
|
248
248
|
{ successMessage: "Email sent." },
|
|
249
249
|
async (params) => {
|
|
250
250
|
const { email, metadata } = params;
|
|
@@ -257,7 +257,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
257
257
|
}
|
|
258
258
|
async sendSms(input) {
|
|
259
259
|
return this.handleAction(
|
|
260
|
-
{ data: input, schema:
|
|
260
|
+
{ data: input, schema: webhook_connector.sendSmsInputSchema },
|
|
261
261
|
{ successMessage: "SMS sent." },
|
|
262
262
|
async (params) => {
|
|
263
263
|
const {
|
|
@@ -294,16 +294,225 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
|
|
|
294
294
|
return this.handleAction(
|
|
295
295
|
{
|
|
296
296
|
data: input,
|
|
297
|
-
schema:
|
|
297
|
+
schema: webhook_connector.sendSlackInputSchema
|
|
298
298
|
},
|
|
299
299
|
{ successMessage: "Slack sent." },
|
|
300
300
|
async (params) => {
|
|
301
|
-
const { slack } = params;
|
|
301
|
+
const { slack, metadata } = params;
|
|
302
302
|
await this.slackConnector.sendNotificationToAllSlack(slack.message);
|
|
303
|
+
await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
|
|
304
|
+
logType: "event",
|
|
305
|
+
service: "notification",
|
|
306
|
+
correlationId: backendSdk.uuidv4(),
|
|
307
|
+
eventType: "notification.slack_sent",
|
|
308
|
+
actorUserId: metadata?.initiator?.userId,
|
|
309
|
+
actorEmail: metadata?.initiator?.userId || "SYSTEM",
|
|
310
|
+
targetType: "slack",
|
|
311
|
+
targetId: "all-channels",
|
|
312
|
+
sourceIp: metadata?.ip,
|
|
313
|
+
userAgent: metadata?.userAgent,
|
|
314
|
+
outcome: "success",
|
|
315
|
+
description: "Slack notification sent",
|
|
316
|
+
details: JSON.stringify({
|
|
317
|
+
message: slack.message
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
async sendWebhook(input) {
|
|
325
|
+
return this.handleAction(
|
|
326
|
+
{ data: input, schema: webhook_connector.sendWebhookInputSchema },
|
|
327
|
+
{ successMessage: "Webhook queued." },
|
|
328
|
+
async (params) => {
|
|
329
|
+
const { webhook, metadata } = params;
|
|
330
|
+
await this.pushToQueue(
|
|
331
|
+
this.env.NOTIFICATIONS_QUEUE,
|
|
332
|
+
{
|
|
333
|
+
type: "webhook",
|
|
334
|
+
payload: {
|
|
335
|
+
webhook
|
|
336
|
+
},
|
|
337
|
+
metadata
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
return {};
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
async _sendWebhook(input) {
|
|
345
|
+
return this.handleAction(
|
|
346
|
+
{ data: input, schema: webhook_connector.sendWebhookInputSchema },
|
|
347
|
+
{ successMessage: "Webhook sent." },
|
|
348
|
+
async (params) => {
|
|
349
|
+
const {
|
|
350
|
+
webhook,
|
|
351
|
+
metadata: {
|
|
352
|
+
ip,
|
|
353
|
+
userAgent,
|
|
354
|
+
initiator: { userId }
|
|
355
|
+
}
|
|
356
|
+
} = params;
|
|
357
|
+
if (!this.webhookConnector) {
|
|
358
|
+
const privateKey = (await this.env.SECRETS_STORE.get({
|
|
359
|
+
secretName: "NOTIFICATION_SERVICE_WEBHOOK_SIGNING_KEY"
|
|
360
|
+
})).data?.secretValue;
|
|
361
|
+
if (!privateKey) {
|
|
362
|
+
throw backendSdk.createInternalError(null, {
|
|
363
|
+
message: "NOTIFICATION_SERVICE_WEBHOOK_SIGNING_KEY is not configured",
|
|
364
|
+
code: "SYS-N-02",
|
|
365
|
+
status: 500
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
this.webhookConnector = new webhook_connector.WebhookConnector(privateKey);
|
|
369
|
+
}
|
|
370
|
+
await this.webhookConnector.sendWebhook(webhook);
|
|
371
|
+
try {
|
|
372
|
+
await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
|
|
373
|
+
logType: "event",
|
|
374
|
+
service: "notification",
|
|
375
|
+
correlationId: backendSdk.uuidv4(),
|
|
376
|
+
eventType: "notification.webhook_sent",
|
|
377
|
+
actorUserId: userId,
|
|
378
|
+
actorEmail: userId || "SYSTEM",
|
|
379
|
+
targetType: "webhook",
|
|
380
|
+
targetId: webhook.url,
|
|
381
|
+
sourceIp: ip,
|
|
382
|
+
userAgent,
|
|
383
|
+
outcome: "success",
|
|
384
|
+
description: `Webhook sent to ${webhook.url}`,
|
|
385
|
+
details: JSON.stringify({
|
|
386
|
+
url: webhook.url
|
|
387
|
+
})
|
|
388
|
+
});
|
|
389
|
+
} catch (e) {
|
|
390
|
+
console.error("Failed to push webhook audit log", e);
|
|
391
|
+
}
|
|
303
392
|
return {};
|
|
304
393
|
}
|
|
305
394
|
);
|
|
306
395
|
}
|
|
396
|
+
async getReceivedEmails(input) {
|
|
397
|
+
return this.handleAction(
|
|
398
|
+
{
|
|
399
|
+
data: input,
|
|
400
|
+
schema: webhook_connector.getReceivedEmailsInputSchema
|
|
401
|
+
},
|
|
402
|
+
{ successMessage: "Emails retrieved." },
|
|
403
|
+
async (params) => {
|
|
404
|
+
const { recipient, limit, includeContent } = params;
|
|
405
|
+
if (!this.env.RECEIVED_EMAILS_KV) {
|
|
406
|
+
throw new Error("RECEIVED_EMAILS_KV binding is not configured");
|
|
407
|
+
}
|
|
408
|
+
const pattern = generateEmailKVPattern(recipient);
|
|
409
|
+
const list = await this.env.RECEIVED_EMAILS_KV.list({
|
|
410
|
+
prefix: pattern,
|
|
411
|
+
limit
|
|
412
|
+
});
|
|
413
|
+
const emails = [];
|
|
414
|
+
for (const key of list.keys) {
|
|
415
|
+
const emailData = await this.env.RECEIVED_EMAILS_KV.get(
|
|
416
|
+
key.name,
|
|
417
|
+
"json"
|
|
418
|
+
);
|
|
419
|
+
if (emailData) {
|
|
420
|
+
const email = emailData;
|
|
421
|
+
if (!includeContent) {
|
|
422
|
+
delete email.text;
|
|
423
|
+
delete email.html;
|
|
424
|
+
delete email.raw;
|
|
425
|
+
}
|
|
426
|
+
emails.push(email);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
emails.sort(
|
|
430
|
+
(a, b) => new Date(b.receivedAt).getTime() - new Date(a.receivedAt).getTime()
|
|
431
|
+
);
|
|
432
|
+
return {
|
|
433
|
+
emails,
|
|
434
|
+
count: emails.length
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
async waitForEmail(input) {
|
|
440
|
+
return this.handleAction(
|
|
441
|
+
{
|
|
442
|
+
data: input,
|
|
443
|
+
schema: webhook_connector.waitForEmailInputSchema
|
|
444
|
+
},
|
|
445
|
+
{ successMessage: "Email check completed." },
|
|
446
|
+
async (params) => {
|
|
447
|
+
const { recipient, subject, timeout, pollInterval } = params;
|
|
448
|
+
if (!this.env.RECEIVED_EMAILS_KV) {
|
|
449
|
+
throw new Error("RECEIVED_EMAILS_KV binding is not configured");
|
|
450
|
+
}
|
|
451
|
+
const maxTimeout = Math.min(timeout || 5e3, 5e3);
|
|
452
|
+
const safeInterval = Math.max(pollInterval || 500, 100);
|
|
453
|
+
const startTime = Date.now();
|
|
454
|
+
const maxIterations = Math.ceil(maxTimeout / safeInterval);
|
|
455
|
+
let iterations = 0;
|
|
456
|
+
while (Date.now() - startTime < maxTimeout && iterations < maxIterations) {
|
|
457
|
+
const pattern = generateEmailKVPattern(recipient);
|
|
458
|
+
const list = await this.env.RECEIVED_EMAILS_KV.list({
|
|
459
|
+
prefix: pattern,
|
|
460
|
+
limit: 50
|
|
461
|
+
});
|
|
462
|
+
for (const key of list.keys) {
|
|
463
|
+
const emailData = await this.env.RECEIVED_EMAILS_KV.get(
|
|
464
|
+
key.name,
|
|
465
|
+
"json"
|
|
466
|
+
);
|
|
467
|
+
if (emailData) {
|
|
468
|
+
const email = emailData;
|
|
469
|
+
if (!subject || email.subject.includes(subject)) {
|
|
470
|
+
return {
|
|
471
|
+
email,
|
|
472
|
+
found: true
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
iterations++;
|
|
478
|
+
if (iterations < maxIterations && Date.now() - startTime < maxTimeout - safeInterval) {
|
|
479
|
+
await new Promise((resolve) => setTimeout(resolve, safeInterval));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
email: null,
|
|
484
|
+
found: false
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
async clearReceivedEmails(input) {
|
|
490
|
+
return this.handleAction(
|
|
491
|
+
{
|
|
492
|
+
data: input,
|
|
493
|
+
schema: webhook_connector.clearReceivedEmailsInputSchema
|
|
494
|
+
},
|
|
495
|
+
{ successMessage: "Emails cleared." },
|
|
496
|
+
async (params) => {
|
|
497
|
+
const { recipient } = params;
|
|
498
|
+
if (!this.env.RECEIVED_EMAILS_KV) {
|
|
499
|
+
throw new Error("RECEIVED_EMAILS_KV binding is not configured");
|
|
500
|
+
}
|
|
501
|
+
const pattern = generateEmailKVPattern(recipient);
|
|
502
|
+
const list = await this.env.RECEIVED_EMAILS_KV.list({
|
|
503
|
+
prefix: pattern
|
|
504
|
+
});
|
|
505
|
+
let deleted = 0;
|
|
506
|
+
for (const key of list.keys) {
|
|
507
|
+
await this.env.RECEIVED_EMAILS_KV.delete(key.name);
|
|
508
|
+
deleted++;
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
deleted
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
}
|
|
307
516
|
};
|
|
308
517
|
__decorateClass([
|
|
309
518
|
backendSdk.cloudflareQueue({ baseDelay: 60 })
|
|
@@ -315,13 +524,13 @@ __decorateClass([
|
|
|
315
524
|
backendSdk.action("private-send-sms")
|
|
316
525
|
], NotificationServiceBase.prototype, "_sendSms", 1);
|
|
317
526
|
__decorateClass([
|
|
318
|
-
backendSdk.action("
|
|
527
|
+
backendSdk.action("send-email")
|
|
319
528
|
], NotificationServiceBase.prototype, "sendEmail", 1);
|
|
320
529
|
__decorateClass([
|
|
321
|
-
backendSdk.action("
|
|
530
|
+
backendSdk.action("send-email-sync")
|
|
322
531
|
], NotificationServiceBase.prototype, "sendEmailSync", 1);
|
|
323
532
|
__decorateClass([
|
|
324
|
-
backendSdk.action("
|
|
533
|
+
backendSdk.action("send-sms")
|
|
325
534
|
], NotificationServiceBase.prototype, "sendSms", 1);
|
|
326
535
|
__decorateClass([
|
|
327
536
|
backendSdk.action("send-push-notification")
|
|
@@ -329,6 +538,21 @@ __decorateClass([
|
|
|
329
538
|
__decorateClass([
|
|
330
539
|
backendSdk.action("send-slack-notification")
|
|
331
540
|
], NotificationServiceBase.prototype, "sendSlackNotification", 1);
|
|
541
|
+
__decorateClass([
|
|
542
|
+
backendSdk.action("send-webhook")
|
|
543
|
+
], NotificationServiceBase.prototype, "sendWebhook", 1);
|
|
544
|
+
__decorateClass([
|
|
545
|
+
backendSdk.action("private-send-webhook")
|
|
546
|
+
], NotificationServiceBase.prototype, "_sendWebhook", 1);
|
|
547
|
+
__decorateClass([
|
|
548
|
+
backendSdk.action("get-received-emails")
|
|
549
|
+
], NotificationServiceBase.prototype, "getReceivedEmails", 1);
|
|
550
|
+
__decorateClass([
|
|
551
|
+
backendSdk.action("wait-for-email")
|
|
552
|
+
], NotificationServiceBase.prototype, "waitForEmail", 1);
|
|
553
|
+
__decorateClass([
|
|
554
|
+
backendSdk.action("clear-received-emails")
|
|
555
|
+
], NotificationServiceBase.prototype, "clearReceivedEmails", 1);
|
|
332
556
|
NotificationServiceBase = __decorateClass([
|
|
333
557
|
backendSdk.service("notification")
|
|
334
558
|
], NotificationServiceBase);
|