bavimail 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,866 @@
1
+ // src/version.ts
2
+ var VERSION = "0.1.0";
3
+
4
+ // src/errors.ts
5
+ var BavimailError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "BavimailError";
9
+ }
10
+ };
11
+ var APIError = class extends BavimailError {
12
+ constructor(message, statusCode, code = null, category = null, context = null, requestId = null) {
13
+ super(message);
14
+ this.name = "APIError";
15
+ this.statusCode = statusCode;
16
+ this.code = code;
17
+ this.category = category;
18
+ this.context = context;
19
+ this.requestId = requestId;
20
+ }
21
+ };
22
+ var NotFoundError = class extends APIError {
23
+ constructor(message, code = null, category = null, context = null, requestId = null) {
24
+ super(message, 404, code, category, context, requestId);
25
+ this.name = "NotFoundError";
26
+ }
27
+ };
28
+ var ValidationError = class extends APIError {
29
+ constructor(message, code = null, category = null, context = null, requestId = null) {
30
+ super(message, 400, code, category, context, requestId);
31
+ this.name = "ValidationError";
32
+ }
33
+ };
34
+ var ConflictError = class extends APIError {
35
+ constructor(message, code = null, category = null, context = null, requestId = null) {
36
+ super(message, 409, code, category, context, requestId);
37
+ this.name = "ConflictError";
38
+ }
39
+ };
40
+ var AuthenticationError = class extends APIError {
41
+ constructor(message, code = null, category = null, context = null, requestId = null) {
42
+ super(message, 401, code, category, context, requestId);
43
+ this.name = "AuthenticationError";
44
+ }
45
+ };
46
+ var ForbiddenError = class extends APIError {
47
+ constructor(message, code = null, category = null, context = null, requestId = null) {
48
+ super(message, 403, code, category, context, requestId);
49
+ this.name = "ForbiddenError";
50
+ }
51
+ };
52
+ var RateLimitError = class extends APIError {
53
+ constructor(message, code = null, category = null, context = null, requestId = null) {
54
+ super(message, 429, code, category, context, requestId);
55
+ this.name = "RateLimitError";
56
+ }
57
+ };
58
+ var InternalServerError = class extends APIError {
59
+ constructor(message, code = null, category = null, context = null, requestId = null) {
60
+ super(message, 500, code, category, context, requestId);
61
+ this.name = "InternalServerError";
62
+ }
63
+ };
64
+ var ProviderError = class extends APIError {
65
+ constructor(message, statusCode = 502, code = null, category = null, context = null, requestId = null) {
66
+ super(message, statusCode, code, category, context, requestId);
67
+ this.name = "ProviderError";
68
+ }
69
+ };
70
+ var WebhookVerificationError = class extends BavimailError {
71
+ constructor(message) {
72
+ super(message);
73
+ this.name = "WebhookVerificationError";
74
+ }
75
+ };
76
+ var STATUS_TO_FACTORY = {
77
+ 400: (m, c, cat, ctx, r) => new ValidationError(m, c, cat, ctx, r),
78
+ 401: (m, c, cat, ctx, r) => new AuthenticationError(m, c, cat, ctx, r),
79
+ 403: (m, c, cat, ctx, r) => new ForbiddenError(m, c, cat, ctx, r),
80
+ 404: (m, c, cat, ctx, r) => new NotFoundError(m, c, cat, ctx, r),
81
+ 409: (m, c, cat, ctx, r) => new ConflictError(m, c, cat, ctx, r),
82
+ 422: (m, c, cat, ctx, r) => new ValidationError(m, c, cat, ctx, r),
83
+ 429: (m, c, cat, ctx, r) => new RateLimitError(m, c, cat, ctx, r),
84
+ 500: (m, c, cat, ctx, r) => new InternalServerError(m, c, cat, ctx, r),
85
+ 502: (m, c, cat, ctx, r) => new ProviderError(m, 502, c, cat, ctx, r),
86
+ 503: (m, c, cat, ctx, r) => new ProviderError(m, 503, c, cat, ctx, r)
87
+ };
88
+ function raiseForStatus(statusCode, body, requestId) {
89
+ let message = `API error (${statusCode})`;
90
+ let code = null;
91
+ let category = null;
92
+ let context = null;
93
+ if (body && typeof body === "object") {
94
+ const detail = body.detail;
95
+ if (detail && typeof detail === "object" && !Array.isArray(detail)) {
96
+ const d = detail;
97
+ message = d.message ?? message;
98
+ code = d.code ?? null;
99
+ category = d.category ?? null;
100
+ context = d.context ?? null;
101
+ } else if (typeof detail === "string") {
102
+ message = detail;
103
+ } else if (typeof body.error === "string") {
104
+ message = body.error;
105
+ }
106
+ }
107
+ const factory = STATUS_TO_FACTORY[statusCode];
108
+ if (factory) {
109
+ throw factory(message, code, category, context, requestId);
110
+ }
111
+ throw new APIError(message, statusCode, code, category, context, requestId);
112
+ }
113
+
114
+ // src/utils.ts
115
+ function toSnakeCase(str) {
116
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
117
+ }
118
+ function toCamelCase(str) {
119
+ return str.replace(
120
+ /_([a-z0-9])/g,
121
+ (_, letter) => letter.toUpperCase()
122
+ );
123
+ }
124
+ function keysToSnake(obj) {
125
+ if (Array.isArray(obj)) {
126
+ return obj.map(keysToSnake);
127
+ }
128
+ if (obj !== null && typeof obj === "object" && !(obj instanceof Date)) {
129
+ return Object.fromEntries(
130
+ Object.entries(obj).map(([key, value]) => [
131
+ toSnakeCase(key),
132
+ keysToSnake(value)
133
+ ])
134
+ );
135
+ }
136
+ return obj;
137
+ }
138
+ function keysToCamel(obj) {
139
+ if (Array.isArray(obj)) {
140
+ return obj.map(keysToCamel);
141
+ }
142
+ if (obj !== null && typeof obj === "object" && !(obj instanceof Date)) {
143
+ return Object.fromEntries(
144
+ Object.entries(obj).map(([key, value]) => [
145
+ toCamelCase(key),
146
+ keysToCamel(value)
147
+ ])
148
+ );
149
+ }
150
+ return obj;
151
+ }
152
+ function stripUndefined(obj) {
153
+ return Object.fromEntries(
154
+ Object.entries(obj).filter(([, v]) => v !== void 0)
155
+ );
156
+ }
157
+
158
+ // src/http.ts
159
+ var HttpClient = class {
160
+ constructor(options) {
161
+ this.apiKey = options.apiKey;
162
+ this.baseUrl = (options.baseUrl ?? "https://api.bavimail.com").replace(
163
+ /\/$/,
164
+ ""
165
+ );
166
+ }
167
+ headers() {
168
+ return {
169
+ "x-api-key": this.apiKey,
170
+ "Content-Type": "application/json",
171
+ "User-Agent": `bavimail-typescript/${VERSION}`
172
+ };
173
+ }
174
+ async request(method, path, options) {
175
+ const url = this.buildUrl(path, options?.params);
176
+ const init = {
177
+ method,
178
+ headers: this.headers()
179
+ };
180
+ if (options?.body) {
181
+ const cleaned = stripUndefined(options.body);
182
+ init.body = JSON.stringify(keysToSnake(cleaned));
183
+ }
184
+ const response = await fetch(url, init);
185
+ const requestId = response.headers.get("x-request-id");
186
+ if (!response.ok) {
187
+ let body;
188
+ try {
189
+ body = await response.json();
190
+ } catch {
191
+ body = null;
192
+ }
193
+ raiseForStatus(response.status, body, requestId);
194
+ }
195
+ if (response.status === 204) {
196
+ return void 0;
197
+ }
198
+ const json = await response.json();
199
+ return keysToCamel(json);
200
+ }
201
+ async requestBytes(method, path, options) {
202
+ const url = this.buildUrl(path, options?.params);
203
+ const init = {
204
+ method,
205
+ headers: this.headers()
206
+ };
207
+ const response = await fetch(url, init);
208
+ const requestId = response.headers.get("x-request-id");
209
+ if (!response.ok) {
210
+ let body;
211
+ try {
212
+ body = await response.json();
213
+ } catch {
214
+ body = null;
215
+ }
216
+ raiseForStatus(response.status, body, requestId);
217
+ }
218
+ return response.arrayBuffer();
219
+ }
220
+ buildUrl(path, params) {
221
+ const url = new URL(`${this.baseUrl}${path}`);
222
+ if (params) {
223
+ const cleaned = stripUndefined(params);
224
+ for (const [key, value] of Object.entries(cleaned)) {
225
+ if (value === null || value === void 0) continue;
226
+ if (Array.isArray(value)) {
227
+ for (const v of value) {
228
+ url.searchParams.append(key, String(v));
229
+ }
230
+ } else {
231
+ url.searchParams.set(key, String(value));
232
+ }
233
+ }
234
+ }
235
+ return url.toString();
236
+ }
237
+ };
238
+
239
+ // src/resources/base.ts
240
+ var BaseResource = class {
241
+ constructor(http) {
242
+ this.http = http;
243
+ }
244
+ };
245
+
246
+ // src/resources/domains.ts
247
+ var Domains = class extends BaseResource {
248
+ /** Register a new domain. */
249
+ async create(params) {
250
+ return this.http.request("POST", "/domains", {
251
+ body: params
252
+ });
253
+ }
254
+ /** List all domains. */
255
+ async list() {
256
+ return this.http.request("GET", "/domains");
257
+ }
258
+ /** Get a domain by ID. */
259
+ async get(domainId) {
260
+ return this.http.request("GET", `/domains/${domainId}`);
261
+ }
262
+ /** Get DNS setup instructions for a domain. */
263
+ async getSetup(domainId) {
264
+ return this.http.request(
265
+ "GET",
266
+ `/domains/${domainId}/setup`
267
+ );
268
+ }
269
+ /** Get live DNS verification status. */
270
+ async getDnsStatus(domainId, options) {
271
+ return this.http.request(
272
+ "GET",
273
+ `/domains/${domainId}/dns-status`,
274
+ { params: { force_refresh: options?.forceRefresh } }
275
+ );
276
+ }
277
+ /** Trigger domain verification. */
278
+ async verify(domainId, options) {
279
+ return this.http.request(
280
+ "POST",
281
+ `/domains/${domainId}/verify`,
282
+ { body: { force: options?.force ?? false } }
283
+ );
284
+ }
285
+ /** Update domain settings. */
286
+ async update(domainId, params) {
287
+ return this.http.request("PUT", `/domains/${domainId}`, {
288
+ body: params
289
+ });
290
+ }
291
+ /** Delete a domain. */
292
+ async delete(domainId) {
293
+ await this.http.request("DELETE", `/domains/${domainId}`);
294
+ }
295
+ };
296
+
297
+ // src/resources/aliases.ts
298
+ var Aliases = class extends BaseResource {
299
+ /** Create a new email alias. */
300
+ async create(params) {
301
+ return this.http.request("POST", "/aliases", {
302
+ body: params
303
+ });
304
+ }
305
+ /** List aliases, optionally filtered by domain. */
306
+ async list(options) {
307
+ return this.http.request("GET", "/aliases", {
308
+ params: { domain_id: options?.domainId }
309
+ });
310
+ }
311
+ /** Get an alias by ID. */
312
+ async get(aliasId) {
313
+ return this.http.request("GET", `/aliases/${aliasId}`);
314
+ }
315
+ /** Update an alias. */
316
+ async update(aliasId, params) {
317
+ return this.http.request("PUT", `/aliases/${aliasId}`, {
318
+ body: params
319
+ });
320
+ }
321
+ /** Delete an alias. */
322
+ async delete(aliasId) {
323
+ await this.http.request("DELETE", `/aliases/${aliasId}`);
324
+ }
325
+ };
326
+
327
+ // src/resources/emails.ts
328
+ var Emails = class extends BaseResource {
329
+ /** Send an email. */
330
+ async send(params) {
331
+ return this.http.request("POST", "/emails", {
332
+ body: params
333
+ });
334
+ }
335
+ /** List sent emails with optional filters. */
336
+ async list(options) {
337
+ return this.http.request("GET", "/emails", {
338
+ params: {
339
+ alias_id: options?.aliasId,
340
+ limit: options?.limit,
341
+ offset: options?.offset
342
+ }
343
+ });
344
+ }
345
+ /** Get a sent email by ID. */
346
+ async get(emailId) {
347
+ return this.http.request("GET", `/emails/${emailId}`);
348
+ }
349
+ /** Send a batch of emails. */
350
+ async batchSend(emails) {
351
+ return this.http.request(
352
+ "POST",
353
+ "/emails/batch",
354
+ { body: { emails } }
355
+ );
356
+ }
357
+ /** Cancel a scheduled email. */
358
+ async cancel(emailId) {
359
+ return this.http.request(
360
+ "POST",
361
+ `/emails/${emailId}/cancel`
362
+ );
363
+ }
364
+ /** List click events for an email. */
365
+ async listClicks(emailId, options) {
366
+ return this.http.request(
367
+ "GET",
368
+ `/emails/${emailId}/clicks`,
369
+ {
370
+ params: {
371
+ link_id: options?.linkId,
372
+ include_bots: options?.includeBots,
373
+ limit: options?.limit,
374
+ offset: options?.offset
375
+ }
376
+ }
377
+ );
378
+ }
379
+ /** List tracked links in an email. */
380
+ async listLinks(emailId) {
381
+ return this.http.request(
382
+ "GET",
383
+ `/emails/${emailId}/links`
384
+ );
385
+ }
386
+ };
387
+
388
+ // src/resources/inbound-emails.ts
389
+ var InboundEmails = class extends BaseResource {
390
+ /** List inbound emails with optional filters. */
391
+ async list(options) {
392
+ return this.http.request(
393
+ "GET",
394
+ "/inbound-emails",
395
+ {
396
+ params: {
397
+ alias_id: options?.aliasId,
398
+ tag_ids: options?.tagIds,
399
+ tag_match: options?.tagMatch,
400
+ limit: options?.limit,
401
+ offset: options?.offset
402
+ }
403
+ }
404
+ );
405
+ }
406
+ /** Get full details of an inbound email. */
407
+ async get(emailId) {
408
+ return this.http.request(
409
+ "GET",
410
+ `/inbound-emails/${emailId}`
411
+ );
412
+ }
413
+ /** Download the raw RFC822 email. */
414
+ async downloadRaw(emailId) {
415
+ return this.http.requestBytes(
416
+ "GET",
417
+ `/inbound-emails/${emailId}/raw`
418
+ );
419
+ }
420
+ /** Download an attachment by zero-based index. */
421
+ async downloadAttachment(emailId, index) {
422
+ return this.http.requestBytes(
423
+ "GET",
424
+ `/inbound-emails/${emailId}/attachments/${index}`
425
+ );
426
+ }
427
+ /** Delete an inbound email. */
428
+ async delete(emailId) {
429
+ await this.http.request(
430
+ "DELETE",
431
+ `/inbound-emails/${emailId}`
432
+ );
433
+ }
434
+ /** Add tags to an inbound email. */
435
+ async addTags(emailId, tagIds, note) {
436
+ return this.http.request(
437
+ "POST",
438
+ `/inbound-emails/${emailId}/tags`,
439
+ { body: { tagIds, note } }
440
+ );
441
+ }
442
+ /** Get tags for an inbound email. */
443
+ async getTags(emailId) {
444
+ return this.http.request(
445
+ "GET",
446
+ `/inbound-emails/${emailId}/tags`
447
+ );
448
+ }
449
+ /** Replace all tags on an inbound email. */
450
+ async replaceTags(emailId, tagIds, note) {
451
+ return this.http.request(
452
+ "PUT",
453
+ `/inbound-emails/${emailId}/tags`,
454
+ { body: { tagIds, note } }
455
+ );
456
+ }
457
+ /** Remove a single tag from an inbound email. */
458
+ async removeTag(emailId, tagId) {
459
+ await this.http.request(
460
+ "DELETE",
461
+ `/inbound-emails/${emailId}/tags/${tagId}`
462
+ );
463
+ }
464
+ };
465
+
466
+ // src/resources/conversations.ts
467
+ var Conversations = class extends BaseResource {
468
+ /** List conversation threads with optional filters. */
469
+ async list(options) {
470
+ return this.http.request(
471
+ "GET",
472
+ "/conversations",
473
+ {
474
+ params: {
475
+ alias_id: options?.aliasId,
476
+ domain_id: options?.domainId,
477
+ limit: options?.limit,
478
+ offset: options?.offset
479
+ }
480
+ }
481
+ );
482
+ }
483
+ /** Get a conversation with all messages. */
484
+ async get(conversationId) {
485
+ return this.http.request(
486
+ "GET",
487
+ `/conversations/${conversationId}`
488
+ );
489
+ }
490
+ };
491
+
492
+ // src/resources/tags.ts
493
+ var Tags = class extends BaseResource {
494
+ /** Create a new tag. */
495
+ async create(params) {
496
+ return this.http.request("POST", "/tags", {
497
+ body: params
498
+ });
499
+ }
500
+ /** List tags. */
501
+ async list(options) {
502
+ return this.http.request("GET", "/tags", {
503
+ params: { include_hidden: options?.includeHidden }
504
+ });
505
+ }
506
+ /** Get a tag by ID. */
507
+ async get(tagId) {
508
+ return this.http.request("GET", `/tags/${tagId}`);
509
+ }
510
+ /** Update a tag. */
511
+ async update(tagId, params) {
512
+ return this.http.request("PUT", `/tags/${tagId}`, {
513
+ body: params
514
+ });
515
+ }
516
+ /** Delete a tag. */
517
+ async delete(tagId) {
518
+ await this.http.request("DELETE", `/tags/${tagId}`);
519
+ }
520
+ };
521
+
522
+ // src/resources/webhooks.ts
523
+ var Webhooks = class extends BaseResource {
524
+ /** Create a new webhook subscription. Returns secret (shown once). */
525
+ async create(params) {
526
+ return this.http.request("POST", "/webhooks", {
527
+ body: params
528
+ });
529
+ }
530
+ /** List all webhooks. */
531
+ async list() {
532
+ return this.http.request("GET", "/webhooks");
533
+ }
534
+ /** Get a webhook by ID. */
535
+ async get(webhookId) {
536
+ return this.http.request("GET", `/webhooks/${webhookId}`);
537
+ }
538
+ /** Update a webhook. */
539
+ async update(webhookId, params) {
540
+ return this.http.request(
541
+ "PATCH",
542
+ `/webhooks/${webhookId}`,
543
+ { body: params }
544
+ );
545
+ }
546
+ /** Delete a webhook. */
547
+ async delete(webhookId) {
548
+ await this.http.request("DELETE", `/webhooks/${webhookId}`);
549
+ }
550
+ /** Verify a webhook URL with the verification code. */
551
+ async verify(webhookId, verificationCode) {
552
+ return this.http.request(
553
+ "POST",
554
+ `/webhooks/${webhookId}/verify`,
555
+ { body: { verificationCode } }
556
+ );
557
+ }
558
+ /** Resend the verification code for a webhook. */
559
+ async resendVerification(webhookId) {
560
+ return this.http.request(
561
+ "POST",
562
+ `/webhooks/${webhookId}/resend-verification`
563
+ );
564
+ }
565
+ /** Send a test event to the webhook. */
566
+ async test(webhookId) {
567
+ return this.http.request(
568
+ "POST",
569
+ `/webhooks/${webhookId}/test`
570
+ );
571
+ }
572
+ /** Rotate the webhook's HMAC signing secret. */
573
+ async rotateSecret(webhookId) {
574
+ return this.http.request(
575
+ "POST",
576
+ `/webhooks/${webhookId}/rotate-secret`
577
+ );
578
+ }
579
+ };
580
+
581
+ // src/webhook-verification.ts
582
+ async function verifyWebhookSignature(payload, signature, timestamp, secret, toleranceSeconds = 300) {
583
+ const tsSeconds = parseInt(timestamp, 10);
584
+ if (isNaN(tsSeconds)) {
585
+ throw new WebhookVerificationError("Invalid timestamp");
586
+ }
587
+ const now = Math.floor(Date.now() / 1e3);
588
+ if (Math.abs(now - tsSeconds) > toleranceSeconds) {
589
+ throw new WebhookVerificationError(
590
+ "Timestamp outside tolerance window"
591
+ );
592
+ }
593
+ const message = `${timestamp}.${payload}`;
594
+ const encoder = new TextEncoder();
595
+ const key = await crypto.subtle.importKey(
596
+ "raw",
597
+ encoder.encode(secret),
598
+ { name: "HMAC", hash: "SHA-256" },
599
+ false,
600
+ ["sign"]
601
+ );
602
+ const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
603
+ const expectedHex = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
604
+ const providedHex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
605
+ if (!timingSafeEqual(expectedHex, providedHex)) {
606
+ throw new WebhookVerificationError("Signature mismatch");
607
+ }
608
+ const raw = JSON.parse(payload);
609
+ return keysToCamel(raw);
610
+ }
611
+ function timingSafeEqual(a, b) {
612
+ if (a.length !== b.length) return false;
613
+ let result = 0;
614
+ for (let i = 0; i < a.length; i++) {
615
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
616
+ }
617
+ return result === 0;
618
+ }
619
+
620
+ // src/handler.ts
621
+ async function dispatchEvent(event, handlers, onError) {
622
+ const fns = handlers.get(event.eventType) ?? [];
623
+ for (const handler of fns) {
624
+ try {
625
+ await handler(event);
626
+ } catch (err) {
627
+ if (onError) {
628
+ try {
629
+ await onError(err, event);
630
+ } catch {
631
+ }
632
+ }
633
+ }
634
+ }
635
+ }
636
+ var JSON_HEADERS = { "Content-Type": "application/json" };
637
+ function jsonResponse(status, body) {
638
+ return new Response(JSON.stringify(body), {
639
+ status,
640
+ headers: JSON_HEADERS
641
+ });
642
+ }
643
+ var WebhookRequestHandler = class {
644
+ constructor(options) {
645
+ this.handlers = options.handlers;
646
+ this.secret = options.secret;
647
+ this.path = options.path ?? "/webhooks";
648
+ this.onError = options.onError;
649
+ }
650
+ /** Handle an incoming Web Standard Request. */
651
+ async fetch(request) {
652
+ const url = new URL(request.url);
653
+ if (url.pathname !== this.path) {
654
+ return jsonResponse(404, { error: "Not found" });
655
+ }
656
+ if (request.method === "GET") {
657
+ return jsonResponse(200, { status: "ok" });
658
+ }
659
+ if (request.method !== "POST") {
660
+ return jsonResponse(405, { error: "Method not allowed" });
661
+ }
662
+ const signature = request.headers.get("x-webhook-signature");
663
+ const timestamp = request.headers.get("x-webhook-timestamp");
664
+ if (!signature || !timestamp) {
665
+ return jsonResponse(401, { error: "Missing signature or timestamp" });
666
+ }
667
+ const body = await request.text();
668
+ let event;
669
+ try {
670
+ event = await verifyWebhookSignature(body, signature, timestamp, this.secret);
671
+ } catch {
672
+ return jsonResponse(403, { error: "Invalid signature" });
673
+ }
674
+ await dispatchEvent(event, this.handlers, this.onError);
675
+ return jsonResponse(200, { status: "ok" });
676
+ }
677
+ };
678
+
679
+ // src/listener.ts
680
+ async function startListener(handlers, webhooksResource, options) {
681
+ if (handlers.size === 0) {
682
+ throw new Error(
683
+ "No event handlers registered. Call client.on() before client.listen()."
684
+ );
685
+ }
686
+ if (!options.webhookUrl && !options.secret) {
687
+ throw new Error(
688
+ "Either webhookUrl (auto-create) or secret (pre-existing) is required."
689
+ );
690
+ }
691
+ const port = options.port ?? 8e3;
692
+ const host = options.host ?? "0.0.0.0";
693
+ const path = options.path ?? "/webhooks";
694
+ let secret;
695
+ let webhookId;
696
+ if (options.secret) {
697
+ secret = options.secret;
698
+ } else {
699
+ const eventTypes = [...handlers.keys()];
700
+ const webhook = await webhooksResource.create({
701
+ url: options.webhookUrl,
702
+ eventTypes,
703
+ description: options.description ?? "Auto-created by Bavimail TypeScript SDK"
704
+ });
705
+ secret = webhook.secret;
706
+ webhookId = webhook.id;
707
+ }
708
+ const { createServer } = await import('http');
709
+ const server = createServer(async (req, res) => {
710
+ const reqUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
711
+ if (reqUrl.pathname !== path) {
712
+ res.writeHead(404, { "Content-Type": "application/json" });
713
+ res.end(JSON.stringify({ error: "Not found" }));
714
+ return;
715
+ }
716
+ if (req.method === "GET") {
717
+ res.writeHead(200, { "Content-Type": "application/json" });
718
+ res.end(JSON.stringify({ status: "ok" }));
719
+ return;
720
+ }
721
+ if (req.method !== "POST") {
722
+ res.writeHead(405, { "Content-Type": "application/json" });
723
+ res.end(JSON.stringify({ error: "Method not allowed" }));
724
+ return;
725
+ }
726
+ const chunks = [];
727
+ for await (const chunk of req) {
728
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
729
+ }
730
+ const body = Buffer.concat(chunks).toString("utf-8");
731
+ const signature = req.headers["x-webhook-signature"];
732
+ const timestamp = req.headers["x-webhook-timestamp"];
733
+ if (!signature || !timestamp) {
734
+ res.writeHead(401, { "Content-Type": "application/json" });
735
+ res.end(JSON.stringify({ error: "Missing signature or timestamp" }));
736
+ return;
737
+ }
738
+ let event;
739
+ try {
740
+ event = await verifyWebhookSignature(body, signature, timestamp, secret);
741
+ } catch {
742
+ res.writeHead(403, { "Content-Type": "application/json" });
743
+ res.end(JSON.stringify({ error: "Invalid signature" }));
744
+ return;
745
+ }
746
+ await dispatchEvent(event, handlers, options.onError);
747
+ res.writeHead(200, { "Content-Type": "application/json" });
748
+ res.end(JSON.stringify({ status: "ok" }));
749
+ });
750
+ let shuttingDown = false;
751
+ async function shutdown() {
752
+ if (shuttingDown) return;
753
+ shuttingDown = true;
754
+ server.close();
755
+ if (webhookId && options.cleanup !== false) {
756
+ try {
757
+ await webhooksResource.delete(webhookId);
758
+ } catch {
759
+ }
760
+ }
761
+ }
762
+ const onSignal = () => {
763
+ shutdown();
764
+ };
765
+ process.on("SIGINT", onSignal);
766
+ process.on("SIGTERM", onSignal);
767
+ if (options.signal) {
768
+ if (options.signal.aborted) {
769
+ await shutdown();
770
+ return;
771
+ }
772
+ options.signal.addEventListener("abort", () => {
773
+ shutdown();
774
+ }, { once: true });
775
+ }
776
+ return new Promise((resolve, reject) => {
777
+ server.on("error", reject);
778
+ server.on("close", () => {
779
+ process.off("SIGINT", onSignal);
780
+ process.off("SIGTERM", onSignal);
781
+ resolve();
782
+ });
783
+ server.listen(port, host, () => {
784
+ console.log(`Bavimail webhook listener running on http://${host}:${port}${path}`);
785
+ });
786
+ });
787
+ }
788
+
789
+ // src/client.ts
790
+ var Bavimail = class {
791
+ constructor(options) {
792
+ this._handlers = /* @__PURE__ */ new Map();
793
+ if (!options.apiKey) {
794
+ throw new Error("apiKey is required");
795
+ }
796
+ this.http = new HttpClient({
797
+ apiKey: options.apiKey,
798
+ baseUrl: options.baseUrl
799
+ });
800
+ this.domains = new Domains(this.http);
801
+ this.aliases = new Aliases(this.http);
802
+ this.emails = new Emails(this.http);
803
+ this.inboundEmails = new InboundEmails(this.http);
804
+ this.conversations = new Conversations(this.http);
805
+ this.tags = new Tags(this.http);
806
+ this.webhooks = new Webhooks(this.http);
807
+ }
808
+ on(eventType, handler) {
809
+ const types = Array.isArray(eventType) ? eventType : [eventType];
810
+ for (const et of types) {
811
+ const list = this._handlers.get(et) ?? [];
812
+ list.push(handler);
813
+ this._handlers.set(et, list);
814
+ }
815
+ }
816
+ /**
817
+ * Start a standalone HTTP server that receives and dispatches webhook events.
818
+ *
819
+ * If `webhookUrl` is provided, a webhook is auto-created via the API and
820
+ * cleaned up on shutdown. If `secret` is provided instead, the server uses
821
+ * the pre-existing webhook secret directly.
822
+ */
823
+ async listen(options) {
824
+ return startListener(this._handlers, this.webhooks, options);
825
+ }
826
+ /**
827
+ * Create a Web Standard webhook request handler for framework integration.
828
+ *
829
+ * The returned handler's `.fetch(request)` method accepts a `Request` and
830
+ * returns a `Response`, compatible with Next.js App Router, Hono, Bun, Deno,
831
+ * Cloudflare Workers, and similar runtimes.
832
+ */
833
+ createHandler(options) {
834
+ return new WebhookRequestHandler({
835
+ handlers: this._handlers,
836
+ secret: options.secret,
837
+ path: options.path,
838
+ onError: options.onError
839
+ });
840
+ }
841
+ };
842
+
843
+ // src/pagination.ts
844
+ async function* iterPages(method, options) {
845
+ const pageSize = options?.pageSize ?? 50;
846
+ let offset = 0;
847
+ while (true) {
848
+ const params = {
849
+ ...options?.params,
850
+ limit: pageSize,
851
+ offset
852
+ };
853
+ const page = await method(params);
854
+ for (const item of page) {
855
+ yield item;
856
+ }
857
+ if (page.length < pageSize) {
858
+ break;
859
+ }
860
+ offset += pageSize;
861
+ }
862
+ }
863
+
864
+ export { APIError, AuthenticationError, Bavimail, BavimailError, ConflictError, ForbiddenError, InternalServerError, NotFoundError, ProviderError, RateLimitError, VERSION, ValidationError, WebhookRequestHandler, WebhookVerificationError, iterPages, verifyWebhookSignature };
865
+ //# sourceMappingURL=index.js.map
866
+ //# sourceMappingURL=index.js.map