@develit-services/notification 0.5.1 → 0.6.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.
Files changed (35) hide show
  1. package/README.md +62 -5
  2. package/dist/database/schema.cjs +4 -6
  3. package/dist/database/schema.d.cts +6 -2
  4. package/dist/database/schema.d.mts +6 -2
  5. package/dist/database/schema.d.ts +6 -2
  6. package/dist/database/schema.mjs +5 -3
  7. package/dist/export/worker.cjs +260 -65
  8. package/dist/export/worker.d.cts +22 -3
  9. package/dist/export/worker.d.mts +22 -3
  10. package/dist/export/worker.d.ts +22 -3
  11. package/dist/export/worker.mjs +252 -57
  12. package/dist/export/wrangler.cjs +19 -1
  13. package/dist/export/wrangler.d.cts +5 -1
  14. package/dist/export/wrangler.d.mts +5 -1
  15. package/dist/export/wrangler.d.ts +5 -1
  16. package/dist/export/wrangler.mjs +19 -1
  17. package/dist/shared/{notification.B0pktSz9.cjs → notification.BLPB8Ib2.cjs} +79 -0
  18. package/dist/shared/{notification.DWuoMDHY.d.ts → notification.BiG4Q650.d.cts} +98 -6
  19. package/dist/shared/{notification.B4ZLWDCP.d.cts → notification.BiG4Q650.d.mts} +98 -6
  20. package/dist/shared/{notification.CpFoKjoN.d.mts → notification.BiG4Q650.d.ts} +98 -6
  21. package/dist/shared/{notification.CmITLO7E.mjs → notification.CP_hFlNt.mjs} +75 -1
  22. package/dist/shared/{notification.BB9Jl8DI.d.mts → notification.CdlaOUd0.d.cts} +3 -0
  23. package/dist/shared/{notification.BB9Jl8DI.d.ts → notification.CdlaOUd0.d.mts} +3 -0
  24. package/dist/shared/{notification.BB9Jl8DI.d.cts → notification.CdlaOUd0.d.ts} +3 -0
  25. package/dist/types.cjs +19 -19
  26. package/dist/types.d.cts +5 -16
  27. package/dist/types.d.mts +5 -16
  28. package/dist/types.d.ts +5 -16
  29. package/dist/types.mjs +2 -10
  30. package/package.json +3 -3
  31. package/dist/shared/notification.4b3eUEIG.cjs +0 -22
  32. package/dist/shared/notification.BWLPh6Gb.d.cts +0 -140
  33. package/dist/shared/notification.BWLPh6Gb.d.mts +0 -140
  34. package/dist/shared/notification.BWLPh6Gb.d.ts +0 -140
  35. 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 → _sendEmail()
55
- ├─ sms → _sendSms()
56
- ├─ slack → sendSlackNotification()
57
- └─ push _sendPushNotification() (501)
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 |
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const database_schema = require('../shared/notification.4b3eUEIG.cjs');
4
- require('@develit-io/backend-sdk');
5
- require('drizzle-orm/sqlite-core');
3
+ const schema = {
4
+ __proto__: null
5
+ };
6
6
 
7
-
8
-
9
- exports.auditLog = database_schema.auditLog;
7
+ exports.schema = schema;
@@ -1,2 +1,6 @@
1
- export { a as auditLog } from '../shared/notification.BWLPh6Gb.cjs';
2
- import 'drizzle-orm/sqlite-core';
1
+ declare namespace schema {
2
+ export {
3
+ };
4
+ }
5
+
6
+ export { schema as s };
@@ -1,2 +1,6 @@
1
- export { a as auditLog } from '../shared/notification.BWLPh6Gb.mjs';
2
- import 'drizzle-orm/sqlite-core';
1
+ declare namespace schema {
2
+ export {
3
+ };
4
+ }
5
+
6
+ export { schema as s };
@@ -1,2 +1,6 @@
1
- export { a as auditLog } from '../shared/notification.BWLPh6Gb.js';
2
- import 'drizzle-orm/sqlite-core';
1
+ declare namespace schema {
2
+ export {
3
+ };
4
+ }
5
+
6
+ export { schema as s };
@@ -1,3 +1,5 @@
1
- export { a as auditLog } from '../shared/notification.C0X8Orrh.mjs';
2
- import '@develit-io/backend-sdk';
3
- import 'drizzle-orm/sqlite-core';
1
+ const schema = {
2
+ __proto__: null
3
+ };
4
+
5
+ export { schema as s };
@@ -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('../shared/notification.4b3eUEIG.cjs');
6
+ const database_schema = require('../database/schema.cjs');
7
7
  require('drizzle-orm');
8
8
  require('drizzle-orm/sqlite-core');
9
- const slack_connector = require('../shared/notification.B0pktSz9.cjs');
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 = [slack_connector.EcomailConnector].find(
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
- const createAuditLogCommand = async ({
37
- db,
38
- auditLog: {
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 = [slack_connector.TwilioConnector].find(
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 slack_connector.SlackConnector(this.env.SLACK_WEBHOOK);
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: slack_connector.sendEmailInputSchema },
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: { service: service2, userId }
133
+ initiator: { userId }
149
134
  }
150
135
  } = params;
151
136
  if (!this.emailConnector) {
@@ -160,26 +145,31 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
160
145
  );
161
146
  }
162
147
  await this.emailConnector.sendEmail(email);
163
- const { command } = await createAuditLogCommand({
164
- db: this.db,
165
- auditLog: {
166
- id: backendSdk.uuidv4(),
167
- event: "EMAIL",
168
- ip,
169
- initiatorService: service2,
170
- initiatorUserId: userId,
171
- userAgent,
172
- description: JSON.stringify(input)
173
- }
148
+ await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
149
+ logType: "event",
150
+ service: "notification",
151
+ correlationId: backendSdk.uuidv4(),
152
+ eventType: "notification.email_sent",
153
+ actorUserId: userId,
154
+ actorEmail: userId || "SYSTEM",
155
+ targetType: "email",
156
+ targetId: email.to,
157
+ sourceIp: ip,
158
+ userAgent,
159
+ outcome: "success",
160
+ description: `Email sent to ${email.to}`,
161
+ details: JSON.stringify({
162
+ subject: email.subject,
163
+ templateId: email.templateId
164
+ })
174
165
  });
175
- await this.db.batch([command]);
176
166
  return {};
177
167
  }
178
168
  );
179
169
  }
180
170
  async _sendSms(input) {
181
171
  return this.handleAction(
182
- { data: input, schema: slack_connector.sendSmsInputSchema },
172
+ { data: input, schema: webhook_connector.sendSmsInputSchema },
183
173
  { successMessage: "Sms sent." },
184
174
  async (params) => {
185
175
  const {
@@ -187,7 +177,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
187
177
  metadata: {
188
178
  ip,
189
179
  userAgent,
190
- initiator: { service: service2, userId }
180
+ initiator: { userId }
191
181
  }
192
182
  } = params;
193
183
  if (!this.smsConnector) {
@@ -205,26 +195,28 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
205
195
  );
206
196
  }
207
197
  await this.smsConnector.sendSms({ message, to });
208
- const { command } = await createAuditLogCommand({
209
- db: this.db,
210
- auditLog: {
211
- id: backendSdk.uuidv4(),
212
- event: "SMS",
213
- ip,
214
- userAgent,
215
- initiatorService: service2,
216
- initiatorUserId: userId,
217
- description: JSON.stringify(input)
218
- }
198
+ await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
199
+ logType: "event",
200
+ service: "notification",
201
+ correlationId: backendSdk.uuidv4(),
202
+ eventType: "notification.sms_sent",
203
+ actorUserId: userId,
204
+ actorEmail: userId || "SYSTEM",
205
+ targetType: "sms",
206
+ targetId: to,
207
+ sourceIp: ip,
208
+ userAgent,
209
+ outcome: "success",
210
+ description: `SMS sent to ${to}`,
211
+ details: JSON.stringify({ message })
219
212
  });
220
- await this.db.batch([command]);
221
213
  return {};
222
214
  }
223
215
  );
224
216
  }
225
217
  async sendEmail(input) {
226
218
  return this.handleAction(
227
- { data: input, schema: slack_connector.sendEmailInputSchema },
219
+ { data: input, schema: webhook_connector.sendEmailInputSchema },
228
220
  { successMessage: "Email sent." },
229
221
  async (params) => {
230
222
  const { email, metadata } = params;
@@ -244,7 +236,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
244
236
  }
245
237
  async sendEmailSync(input) {
246
238
  return this.handleAction(
247
- { data: input, schema: slack_connector.sendEmailInputSchema },
239
+ { data: input, schema: webhook_connector.sendEmailInputSchema },
248
240
  { successMessage: "Email sent." },
249
241
  async (params) => {
250
242
  const { email, metadata } = params;
@@ -257,7 +249,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
257
249
  }
258
250
  async sendSms(input) {
259
251
  return this.handleAction(
260
- { data: input, schema: slack_connector.sendSmsInputSchema },
252
+ { data: input, schema: webhook_connector.sendSmsInputSchema },
261
253
  { successMessage: "SMS sent." },
262
254
  async (params) => {
263
255
  const {
@@ -294,7 +286,7 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
294
286
  return this.handleAction(
295
287
  {
296
288
  data: input,
297
- schema: slack_connector.sendSlackInputSchema
289
+ schema: webhook_connector.sendSlackInputSchema
298
290
  },
299
291
  { successMessage: "Slack sent." },
300
292
  async (params) => {
@@ -304,6 +296,194 @@ let NotificationServiceBase = class extends backendSdk.develitWorker(
304
296
  }
305
297
  );
306
298
  }
299
+ async sendWebhook(input) {
300
+ return this.handleAction(
301
+ { data: input, schema: webhook_connector.sendWebhookInputSchema },
302
+ { successMessage: "Webhook queued." },
303
+ async (params) => {
304
+ const { webhook, metadata } = params;
305
+ await this.pushToQueue(
306
+ this.env.NOTIFICATIONS_QUEUE,
307
+ {
308
+ type: "webhook",
309
+ payload: {
310
+ webhook
311
+ },
312
+ metadata
313
+ }
314
+ );
315
+ return {};
316
+ }
317
+ );
318
+ }
319
+ async _sendWebhook(input) {
320
+ return this.handleAction(
321
+ { data: input, schema: webhook_connector.sendWebhookInputSchema },
322
+ { successMessage: "Webhook sent." },
323
+ async (params) => {
324
+ const {
325
+ webhook,
326
+ metadata: {
327
+ ip,
328
+ userAgent,
329
+ initiator: { userId }
330
+ }
331
+ } = params;
332
+ if (!this.webhookConnector) {
333
+ const privateKey = (await this.env.SECRETS_STORE.get({
334
+ secretName: "NOTIFICATION_SERVICE_WEBHOOK_SIGNING_KEY"
335
+ })).data?.secretValue;
336
+ if (!privateKey) {
337
+ throw backendSdk.createInternalError(null, {
338
+ message: "NOTIFICATION_SERVICE_WEBHOOK_SIGNING_KEY is not configured",
339
+ code: "SYS-N-02",
340
+ status: 500
341
+ });
342
+ }
343
+ this.webhookConnector = new webhook_connector.WebhookConnector(privateKey);
344
+ }
345
+ await this.webhookConnector.sendWebhook(webhook);
346
+ await this.pushToQueue(this.env.AUDIT_LOGS_QUEUE, {
347
+ logType: "event",
348
+ service: "notification",
349
+ correlationId: backendSdk.uuidv4(),
350
+ eventType: "notification.webhook_sent",
351
+ actorUserId: userId,
352
+ actorEmail: userId || "SYSTEM",
353
+ targetType: "webhook",
354
+ targetId: webhook.url,
355
+ sourceIp: ip,
356
+ userAgent,
357
+ outcome: "success",
358
+ description: `Webhook sent to ${webhook.url}`,
359
+ details: JSON.stringify({
360
+ url: webhook.url
361
+ })
362
+ });
363
+ return {};
364
+ }
365
+ );
366
+ }
367
+ async getReceivedEmails(input) {
368
+ return this.handleAction(
369
+ {
370
+ data: input,
371
+ schema: webhook_connector.getReceivedEmailsInputSchema
372
+ },
373
+ { successMessage: "Emails retrieved." },
374
+ async (params) => {
375
+ const { recipient, limit, includeContent } = params;
376
+ if (!this.env.RECEIVED_EMAILS_KV) {
377
+ throw new Error("RECEIVED_EMAILS_KV binding is not configured");
378
+ }
379
+ const pattern = generateEmailKVPattern(recipient);
380
+ const list = await this.env.RECEIVED_EMAILS_KV.list({
381
+ prefix: pattern,
382
+ limit
383
+ });
384
+ const emails = [];
385
+ for (const key of list.keys) {
386
+ const emailData = await this.env.RECEIVED_EMAILS_KV.get(
387
+ key.name,
388
+ "json"
389
+ );
390
+ if (emailData) {
391
+ const email = emailData;
392
+ if (!includeContent) {
393
+ delete email.text;
394
+ delete email.html;
395
+ delete email.raw;
396
+ }
397
+ emails.push(email);
398
+ }
399
+ }
400
+ emails.sort(
401
+ (a, b) => new Date(b.receivedAt).getTime() - new Date(a.receivedAt).getTime()
402
+ );
403
+ return {
404
+ emails,
405
+ count: emails.length
406
+ };
407
+ }
408
+ );
409
+ }
410
+ async waitForEmail(input) {
411
+ return this.handleAction(
412
+ {
413
+ data: input,
414
+ schema: webhook_connector.waitForEmailInputSchema
415
+ },
416
+ { successMessage: "Email check completed." },
417
+ async (params) => {
418
+ const { recipient, subject, timeout, pollInterval } = params;
419
+ if (!this.env.RECEIVED_EMAILS_KV) {
420
+ throw new Error("RECEIVED_EMAILS_KV binding is not configured");
421
+ }
422
+ const maxTimeout = Math.min(timeout || 5e3, 5e3);
423
+ const safeInterval = Math.max(pollInterval || 500, 100);
424
+ const startTime = Date.now();
425
+ const maxIterations = Math.ceil(maxTimeout / safeInterval);
426
+ let iterations = 0;
427
+ while (Date.now() - startTime < maxTimeout && iterations < maxIterations) {
428
+ const pattern = generateEmailKVPattern(recipient);
429
+ const list = await this.env.RECEIVED_EMAILS_KV.list({
430
+ prefix: pattern,
431
+ limit: 50
432
+ });
433
+ for (const key of list.keys) {
434
+ const emailData = await this.env.RECEIVED_EMAILS_KV.get(
435
+ key.name,
436
+ "json"
437
+ );
438
+ if (emailData) {
439
+ const email = emailData;
440
+ if (!subject || email.subject.includes(subject)) {
441
+ return {
442
+ email,
443
+ found: true
444
+ };
445
+ }
446
+ }
447
+ }
448
+ iterations++;
449
+ if (iterations < maxIterations && Date.now() - startTime < maxTimeout - safeInterval) {
450
+ await new Promise((resolve) => setTimeout(resolve, safeInterval));
451
+ }
452
+ }
453
+ return {
454
+ email: null,
455
+ found: false
456
+ };
457
+ }
458
+ );
459
+ }
460
+ async clearReceivedEmails(input) {
461
+ return this.handleAction(
462
+ {
463
+ data: input,
464
+ schema: webhook_connector.clearReceivedEmailsInputSchema
465
+ },
466
+ { successMessage: "Emails cleared." },
467
+ async (params) => {
468
+ const { recipient } = params;
469
+ if (!this.env.RECEIVED_EMAILS_KV) {
470
+ throw new Error("RECEIVED_EMAILS_KV binding is not configured");
471
+ }
472
+ const pattern = generateEmailKVPattern(recipient);
473
+ const list = await this.env.RECEIVED_EMAILS_KV.list({
474
+ prefix: pattern
475
+ });
476
+ let deleted = 0;
477
+ for (const key of list.keys) {
478
+ await this.env.RECEIVED_EMAILS_KV.delete(key.name);
479
+ deleted++;
480
+ }
481
+ return {
482
+ deleted
483
+ };
484
+ }
485
+ );
486
+ }
307
487
  };
308
488
  __decorateClass([
309
489
  backendSdk.cloudflareQueue({ baseDelay: 60 })
@@ -315,13 +495,13 @@ __decorateClass([
315
495
  backendSdk.action("private-send-sms")
316
496
  ], NotificationServiceBase.prototype, "_sendSms", 1);
317
497
  __decorateClass([
318
- backendSdk.action("public-send-email")
498
+ backendSdk.action("send-email")
319
499
  ], NotificationServiceBase.prototype, "sendEmail", 1);
320
500
  __decorateClass([
321
- backendSdk.action("public-send-email-sync")
501
+ backendSdk.action("send-email-sync")
322
502
  ], NotificationServiceBase.prototype, "sendEmailSync", 1);
323
503
  __decorateClass([
324
- backendSdk.action("public-send-sms")
504
+ backendSdk.action("send-sms")
325
505
  ], NotificationServiceBase.prototype, "sendSms", 1);
326
506
  __decorateClass([
327
507
  backendSdk.action("send-push-notification")
@@ -329,6 +509,21 @@ __decorateClass([
329
509
  __decorateClass([
330
510
  backendSdk.action("send-slack-notification")
331
511
  ], NotificationServiceBase.prototype, "sendSlackNotification", 1);
512
+ __decorateClass([
513
+ backendSdk.action("send-webhook")
514
+ ], NotificationServiceBase.prototype, "sendWebhook", 1);
515
+ __decorateClass([
516
+ backendSdk.action("private-send-webhook")
517
+ ], NotificationServiceBase.prototype, "_sendWebhook", 1);
518
+ __decorateClass([
519
+ backendSdk.action("get-received-emails")
520
+ ], NotificationServiceBase.prototype, "getReceivedEmails", 1);
521
+ __decorateClass([
522
+ backendSdk.action("wait-for-email")
523
+ ], NotificationServiceBase.prototype, "waitForEmail", 1);
524
+ __decorateClass([
525
+ backendSdk.action("clear-received-emails")
526
+ ], NotificationServiceBase.prototype, "clearReceivedEmails", 1);
332
527
  NotificationServiceBase = __decorateClass([
333
528
  backendSdk.service("notification")
334
529
  ], NotificationServiceBase);
@@ -1,16 +1,18 @@
1
1
  import * as _develit_io_backend_sdk from '@develit-io/backend-sdk';
2
2
  import { IRPCResponse } from '@develit-io/backend-sdk';
3
- import { S as SlackConnector, I as IEmailConnector, a as ISmsConnector, t as tables, N as NotificationQueueMessage, b as SendEmailInput, c as SendEmailOutput, d as SendSmsInput, e as SendSmsOutput, f as SendSlackInput, g as SendSlackOutput } from '../shared/notification.B4ZLWDCP.cjs';
3
+ import { s as schema } from '../database/schema.cjs';
4
+ import { S as SlackConnector, W as WebhookConnector, I as IEmailConnector, a as ISmsConnector, N as NotificationQueueMessage, b as SendEmailInput, c as SendEmailOutput, d as SendSmsInput, e as SendSmsOutput, f as SendSlackInput, g as SendSlackOutput, h as SendWebhookInput, i as SendWebhookOutput, G as GetReceivedEmailsInput, j as GetReceivedEmailsOutput, k as WaitForEmailInput, l as WaitForEmailOutput, C as ClearReceivedEmailsInput, m as ClearReceivedEmailsOutput } from '../shared/notification.BiG4Q650.cjs';
4
5
  import { WorkerEntrypoint } from 'cloudflare:workers';
5
6
  import { DrizzleD1Database } from 'drizzle-orm/d1';
6
- import '../shared/notification.BWLPh6Gb.cjs';
7
- import 'drizzle-orm/sqlite-core';
8
7
  import 'zod';
9
8
  import 'zod/v4';
10
9
 
10
+ declare const tables: typeof schema;
11
+
11
12
  declare const NotificationServiceBase_base: (abstract new (ctx: ExecutionContext, env: NotificationEnv) => WorkerEntrypoint<NotificationEnv, {}>) & (abstract new (...args: any[]) => _develit_io_backend_sdk.DevelitWorkerMethods);
12
13
  declare class NotificationServiceBase extends NotificationServiceBase_base {
13
14
  readonly slackConnector: SlackConnector;
15
+ protected webhookConnector: WebhookConnector;
14
16
  protected emailConnector: IEmailConnector;
15
17
  protected smsConnector: ISmsConnector;
16
18
  readonly db: DrizzleD1Database<typeof tables>;
@@ -23,6 +25,23 @@ declare class NotificationServiceBase extends NotificationServiceBase_base {
23
25
  sendSms(input: SendSmsInput): Promise<IRPCResponse<SendSmsOutput>>;
24
26
  protected _sendPushNotification(): Promise<IRPCResponse<{}>>;
25
27
  sendSlackNotification(input: SendSlackInput): Promise<IRPCResponse<SendSlackOutput>>;
28
+ sendWebhook(input: SendWebhookInput): Promise<IRPCResponse<SendWebhookOutput>>;
29
+ protected _sendWebhook(input: SendWebhookInput): Promise<IRPCResponse<SendWebhookOutput>>;
30
+ /**
31
+ * Get received emails from KV storage
32
+ * Used primarily for E2E testing
33
+ */
34
+ getReceivedEmails(input: GetReceivedEmailsInput): Promise<IRPCResponse<GetReceivedEmailsOutput>>;
35
+ /**
36
+ * Wait for an email to arrive (polling helper for E2E tests)
37
+ * NOTE: Limited to 5 second timeout to prevent blocking
38
+ */
39
+ waitForEmail(input: WaitForEmailInput): Promise<IRPCResponse<WaitForEmailOutput>>;
40
+ /**
41
+ * Clear received emails for a recipient (cleanup helper)
42
+ * Used primarily for E2E testing
43
+ */
44
+ clearReceivedEmails(input: ClearReceivedEmailsInput): Promise<IRPCResponse<ClearReceivedEmailsOutput>>;
26
45
  }
27
46
  declare function defineNotificationService(): new (ctx: ExecutionContext, env: NotificationEnv) => NotificationServiceBase;
28
47