@primitivedotdev/sdk 0.7.0 → 0.9.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/README.md +114 -210
- package/dist/address-parser-BYn8oW5r.js +111 -0
- package/dist/api/generated/index.js +1 -1
- package/dist/api/generated/sdk.gen.js +17 -0
- package/dist/api/index.d.ts +2 -1877
- package/dist/api/index.js +255 -0
- package/dist/api-COSr-Fqm.js +1311 -0
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/contract/index.d.ts +6 -8
- package/dist/contract/index.js +28 -15
- package/dist/{index-DLmAI4UQ.d.ts → index-CbEivn3S.d.ts} +13 -30
- package/dist/index-DVow4Fjd.d.ts +2140 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +10 -3
- package/dist/oclif/api-command.js +89 -1
- package/dist/openapi/index.d.ts +8 -3
- package/dist/openapi/openapi.generated.js +412 -1
- package/dist/openapi/operations.generated.js +255 -0
- package/dist/parser/address-parser.js +129 -0
- package/dist/parser/index.d.ts +4 -19
- package/dist/parser/index.js +7 -122
- package/dist/received-email-D6tKtWwW.js +69 -0
- package/dist/received-email-DNjpq_Wt.d.ts +37 -0
- package/dist/{types-CKFmgitP.d.ts → types-9vXGZjPd.d.ts} +3 -19
- package/dist/types.generated.js +7 -0
- package/dist/types.js +53 -0
- package/dist/webhook/index.d.ts +4 -3
- package/dist/webhook/index.js +3 -3
- package/dist/webhook/received-email.js +82 -0
- package/dist/{webhook-COe5N_Uj.js → webhook-zkN4wUTs.js} +119 -81
- package/oclif.manifest.json +54 -8
- package/package.json +5 -2
- package/dist/chunk-Cl8Af3a2.js +0 -11
package/dist/parser/index.js
CHANGED
|
@@ -1,114 +1,9 @@
|
|
|
1
|
+
import { n as parseFromHeaderLoose, t as parseFromHeader } from "../address-parser-BYn8oW5r.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
|
-
import addressparser from "nodemailer/lib/addressparser/index.js";
|
|
3
|
-
import isEmail from "validator/lib/isEmail.js";
|
|
4
3
|
import { createGzip } from "node:zlib";
|
|
5
4
|
import { pack } from "tar-stream";
|
|
6
5
|
import { simpleParser } from "mailparser";
|
|
7
6
|
import DOMPurify from "isomorphic-dompurify";
|
|
8
|
-
|
|
9
|
-
//#region src/parser/address-parser.ts
|
|
10
|
-
const MAX_HEADER_LENGTH = 998;
|
|
11
|
-
const IS_EMAIL_OPTIONS = {
|
|
12
|
-
allow_ip_domain: true,
|
|
13
|
-
require_tld: true,
|
|
14
|
-
allow_display_name: false,
|
|
15
|
-
allow_utf8_local_part: true
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Strict parser for RFC 5322 From-style headers in security-bearing
|
|
19
|
-
* contexts (allowlist gates, permission grants).
|
|
20
|
-
*
|
|
21
|
-
* Rejects, without falling back to a "best guess":
|
|
22
|
-
* - empty / whitespace-only input
|
|
23
|
-
* - inputs longer than RFC 5322's 998-octet line limit
|
|
24
|
-
* - multi-address From (RFC 5322 allows it but it is vanishingly
|
|
25
|
-
* rare and ambiguous as an identity)
|
|
26
|
-
* - group syntax ("Friends: a@b.com, c@d.com;")
|
|
27
|
-
* - any address that fails validator's isEmail check with our chosen
|
|
28
|
-
* options. That covers per-part length limits, dot-atom rules,
|
|
29
|
-
* hostname-label rules, TLD requirement, and other RFC 5321/5322
|
|
30
|
-
* conformance checks.
|
|
31
|
-
*
|
|
32
|
-
* Returns ONLY the validated address, with no display name. Strict
|
|
33
|
-
* exists for gating decisions, where the address is the security-
|
|
34
|
-
* bearing field. Display names from addressparser are not trustworthy
|
|
35
|
-
* here: weird inputs like `Name <user@x.com> <attacker@y.com>` get
|
|
36
|
-
* parsed as a single entry whose `name` silently includes the second
|
|
37
|
-
* address. Surfacing that as a "parsed name" would invite downstream
|
|
38
|
-
* misuse, so we drop it. If you need the name, call
|
|
39
|
-
* {@link parseFromHeaderLoose} alongside (it returns null on failure
|
|
40
|
-
* anyway, so you can still gate on strict's Result).
|
|
41
|
-
*
|
|
42
|
-
* Returns a typed Result so callers can map the failure reason to
|
|
43
|
-
* stable error codes without inspecting message text.
|
|
44
|
-
*/
|
|
45
|
-
function parseFromHeader(header) {
|
|
46
|
-
if (header === null || header === void 0) return {
|
|
47
|
-
ok: false,
|
|
48
|
-
reason: "empty"
|
|
49
|
-
};
|
|
50
|
-
const trimmed = header.trim();
|
|
51
|
-
if (trimmed.length === 0) return {
|
|
52
|
-
ok: false,
|
|
53
|
-
reason: "empty"
|
|
54
|
-
};
|
|
55
|
-
if (Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) return {
|
|
56
|
-
ok: false,
|
|
57
|
-
reason: "too_long"
|
|
58
|
-
};
|
|
59
|
-
const parsed = addressparser(trimmed);
|
|
60
|
-
if (parsed.length > 1) return {
|
|
61
|
-
ok: false,
|
|
62
|
-
reason: "multiple_addresses"
|
|
63
|
-
};
|
|
64
|
-
const entry = parsed[0];
|
|
65
|
-
if (entry === void 0) return {
|
|
66
|
-
ok: false,
|
|
67
|
-
reason: "invalid_address"
|
|
68
|
-
};
|
|
69
|
-
if ("group" in entry) return {
|
|
70
|
-
ok: false,
|
|
71
|
-
reason: "group_syntax"
|
|
72
|
-
};
|
|
73
|
-
if (!isEmail(entry.address, IS_EMAIL_OPTIONS)) return {
|
|
74
|
-
ok: false,
|
|
75
|
-
reason: "invalid_address"
|
|
76
|
-
};
|
|
77
|
-
return {
|
|
78
|
-
ok: true,
|
|
79
|
-
value: { address: entry.address.toLowerCase() }
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Lenient parser for display-only call sites (inbox card "from",
|
|
84
|
-
* log lines, debugging). Returns the first parseable address with its
|
|
85
|
-
* display name, or null.
|
|
86
|
-
*
|
|
87
|
-
* Differences from {@link parseFromHeader}:
|
|
88
|
-
* - Multi-address From returns the first address instead of rejecting
|
|
89
|
-
* - Group syntax is flattened into its member addresses
|
|
90
|
-
* - Returns null instead of a typed reason on failure
|
|
91
|
-
* - Includes the parsed display name in the result
|
|
92
|
-
*
|
|
93
|
-
* Do not use for permission gates or any decision that grants access.
|
|
94
|
-
* That is what {@link parseFromHeader} is for. Names returned here can
|
|
95
|
-
* include addressparser's recovery output (trailing tokens, garbage
|
|
96
|
-
* before the address); treat as opaque text for display.
|
|
97
|
-
*/
|
|
98
|
-
function parseFromHeaderLoose(header) {
|
|
99
|
-
if (header === null || header === void 0) return null;
|
|
100
|
-
const trimmed = header.trim();
|
|
101
|
-
if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) return null;
|
|
102
|
-
const parsed = addressparser(trimmed, { flatten: true });
|
|
103
|
-
const entry = parsed[0];
|
|
104
|
-
if (entry === void 0 || !isEmail(entry.address, IS_EMAIL_OPTIONS)) return null;
|
|
105
|
-
return {
|
|
106
|
-
address: entry.address.toLowerCase(),
|
|
107
|
-
name: entry.name && entry.name.length > 0 ? entry.name : null
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
//#endregion
|
|
112
7
|
//#region src/parser/attachment-bundler.ts
|
|
113
8
|
function appendTarEntry(archive, name, content) {
|
|
114
9
|
return new Promise((resolve, reject) => {
|
|
@@ -185,10 +80,9 @@ async function bundleAttachments(attachments) {
|
|
|
185
80
|
}
|
|
186
81
|
})();
|
|
187
82
|
});
|
|
188
|
-
const sha256 = createHash("sha256").update(tarGzBuffer).digest("hex");
|
|
189
83
|
return {
|
|
190
84
|
tarGzBuffer,
|
|
191
|
-
sha256,
|
|
85
|
+
sha256: createHash("sha256").update(tarGzBuffer).digest("hex"),
|
|
192
86
|
attachmentCount: downloadable.length,
|
|
193
87
|
totalAttachmentBytes
|
|
194
88
|
};
|
|
@@ -218,10 +112,8 @@ function extractAttachmentMetadata(attachments) {
|
|
|
218
112
|
* @returns Storage key in format: attachments/{email_id}_{hash8}.tar.gz
|
|
219
113
|
*/
|
|
220
114
|
function getAttachmentsStorageKey(emailId, sha256) {
|
|
221
|
-
|
|
222
|
-
return `attachments/${emailId}_${hash8}.tar.gz`;
|
|
115
|
+
return `attachments/${emailId}_${sha256.substring(0, 8)}.tar.gz`;
|
|
223
116
|
}
|
|
224
|
-
|
|
225
117
|
//#endregion
|
|
226
118
|
//#region src/parser/sanitize-html.ts
|
|
227
119
|
const ALLOWED_TAGS = [
|
|
@@ -307,8 +199,7 @@ DOMPurify.addHook("uponSanitizeAttribute", (_node, data) => {
|
|
|
307
199
|
});
|
|
308
200
|
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
|
|
309
201
|
if (node.tagName === "A") {
|
|
310
|
-
|
|
311
|
-
if (target === "_blank") node.setAttribute("rel", "noopener noreferrer");
|
|
202
|
+
if (node.getAttribute("target") === "_blank") node.setAttribute("rel", "noopener noreferrer");
|
|
312
203
|
}
|
|
313
204
|
});
|
|
314
205
|
const SANITIZE_OPTIONS = {
|
|
@@ -338,7 +229,6 @@ const SANITIZE_OPTIONS = {
|
|
|
338
229
|
function sanitizeHtml(html) {
|
|
339
230
|
return DOMPurify.sanitize(html, SANITIZE_OPTIONS);
|
|
340
231
|
}
|
|
341
|
-
|
|
342
232
|
//#endregion
|
|
343
233
|
//#region src/parser/attachment-parser.ts
|
|
344
234
|
const SIGNATURE_ARTIFACTS = new Set([
|
|
@@ -449,8 +339,7 @@ function normalizeReferences(refs) {
|
|
|
449
339
|
*/
|
|
450
340
|
function normalizeContentType(contentType) {
|
|
451
341
|
if (!contentType?.trim()) return "application/octet-stream";
|
|
452
|
-
|
|
453
|
-
return mediaType || "application/octet-stream";
|
|
342
|
+
return contentType.split(";")[0].trim().toLowerCase() || "application/octet-stream";
|
|
454
343
|
}
|
|
455
344
|
/**
|
|
456
345
|
* Parse disposition string to typed value.
|
|
@@ -479,8 +368,7 @@ function getHeaderString(value) {
|
|
|
479
368
|
return String(value);
|
|
480
369
|
}
|
|
481
370
|
function getOriginalHeaderValue(parsed, key) {
|
|
482
|
-
const
|
|
483
|
-
const original = headerLines?.find((header) => header.key?.toLowerCase() === key.toLowerCase())?.line;
|
|
371
|
+
const original = parsed.headerLines?.find((header) => header.key?.toLowerCase() === key.toLowerCase())?.line;
|
|
484
372
|
if (!original) return null;
|
|
485
373
|
const separator = original.indexOf(":");
|
|
486
374
|
return separator === -1 ? original : original.slice(separator + 1).trimStart();
|
|
@@ -510,7 +398,6 @@ function sanitizeFilename(filename, partIndex) {
|
|
|
510
398
|
if (!safe) return `attachment_${partIndex}`;
|
|
511
399
|
return safe;
|
|
512
400
|
}
|
|
513
|
-
|
|
514
401
|
//#endregion
|
|
515
402
|
//#region src/parser/email-parser.ts
|
|
516
403
|
/**
|
|
@@ -570,7 +457,6 @@ function structuredHeaderToString(value) {
|
|
|
570
457
|
}
|
|
571
458
|
return JSON.stringify(value);
|
|
572
459
|
}
|
|
573
|
-
|
|
574
460
|
//#endregion
|
|
575
461
|
//#region src/parser/mapping.ts
|
|
576
462
|
/**
|
|
@@ -660,6 +546,5 @@ function requireNonEmptyHeader(value, headerName) {
|
|
|
660
546
|
if (typeof value !== "string" || value.trim() === "") throw new Error(`Parsed email is missing a usable ${headerName} header value`);
|
|
661
547
|
return value;
|
|
662
548
|
}
|
|
663
|
-
|
|
664
549
|
//#endregion
|
|
665
|
-
export { attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
|
|
550
|
+
export { attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { n as parseFromHeaderLoose } from "./address-parser-BYn8oW5r.js";
|
|
2
|
+
//#region src/webhook/received-email.ts
|
|
3
|
+
const REPLY_PREFIX_RE = /^re\s*:/i;
|
|
4
|
+
const FORWARD_PREFIX_RE = /^(fwd?|fw)\s*:/i;
|
|
5
|
+
function normalizeReceivedEmail(event) {
|
|
6
|
+
const receivedBy = event.email.smtp.rcpt_to[0];
|
|
7
|
+
if (!receivedBy) throw new Error("email.smtp.rcpt_to must contain at least one recipient");
|
|
8
|
+
const sender = parseHeaderAddress(event.email.headers.from) ?? {
|
|
9
|
+
address: event.email.smtp.mail_from.trim().toLowerCase(),
|
|
10
|
+
name: null
|
|
11
|
+
};
|
|
12
|
+
const replyTarget = firstStructuredAddress(event.email.parsed.reply_to) ?? sender;
|
|
13
|
+
const subject = event.email.headers.subject ?? null;
|
|
14
|
+
const references = event.email.parsed.references ?? [];
|
|
15
|
+
const messageId = event.email.headers.message_id ?? null;
|
|
16
|
+
return {
|
|
17
|
+
id: event.email.id,
|
|
18
|
+
eventId: event.id,
|
|
19
|
+
receivedAt: event.email.received_at,
|
|
20
|
+
sender,
|
|
21
|
+
replyTarget,
|
|
22
|
+
receivedBy,
|
|
23
|
+
receivedByAll: [...event.email.smtp.rcpt_to],
|
|
24
|
+
subject,
|
|
25
|
+
replySubject: buildReplySubject(subject),
|
|
26
|
+
forwardSubject: buildForwardSubject(subject),
|
|
27
|
+
text: event.email.parsed.body_text ?? null,
|
|
28
|
+
thread: {
|
|
29
|
+
messageId,
|
|
30
|
+
inReplyTo: event.email.parsed.in_reply_to ?? [],
|
|
31
|
+
references
|
|
32
|
+
},
|
|
33
|
+
attachments: event.email.parsed.attachments ?? [],
|
|
34
|
+
auth: event.email.auth,
|
|
35
|
+
analysis: event.email.analysis,
|
|
36
|
+
raw: event
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function buildReplySubject(subject) {
|
|
40
|
+
const trimmed = subject?.trim() ?? "";
|
|
41
|
+
if (trimmed.length === 0) return "Re:";
|
|
42
|
+
return REPLY_PREFIX_RE.test(trimmed) ? trimmed : `Re: ${trimmed}`;
|
|
43
|
+
}
|
|
44
|
+
function buildForwardSubject(subject) {
|
|
45
|
+
const trimmed = subject?.trim() ?? "";
|
|
46
|
+
if (trimmed.length === 0) return "Fwd:";
|
|
47
|
+
return FORWARD_PREFIX_RE.test(trimmed) ? trimmed : `Fwd: ${trimmed}`;
|
|
48
|
+
}
|
|
49
|
+
function formatAddress(address) {
|
|
50
|
+
return address.name ? `${address.name} <${address.address}>` : address.address;
|
|
51
|
+
}
|
|
52
|
+
function firstStructuredAddress(addresses) {
|
|
53
|
+
const address = addresses?.[0];
|
|
54
|
+
if (!address) return null;
|
|
55
|
+
return {
|
|
56
|
+
address: address.address.trim().toLowerCase(),
|
|
57
|
+
name: address.name ?? null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function parseHeaderAddress(value) {
|
|
61
|
+
const parsed = parseFromHeaderLoose(value);
|
|
62
|
+
if (!parsed) return null;
|
|
63
|
+
return {
|
|
64
|
+
address: parsed.address,
|
|
65
|
+
name: parsed.name?.trim() || null
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { parseHeaderAddress as a, normalizeReceivedEmail as i, buildReplySubject as n, formatAddress as r, buildForwardSubject as t };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { M as WebhookAttachment, c as EmailAnalysis, l as EmailAuth, u as EmailReceivedEvent } from "./types-9vXGZjPd.js";
|
|
2
|
+
|
|
3
|
+
//#region src/webhook/received-email.d.ts
|
|
4
|
+
interface ReceivedEmailAddress {
|
|
5
|
+
address: string;
|
|
6
|
+
name: string | null;
|
|
7
|
+
}
|
|
8
|
+
interface ReceivedEmailThread {
|
|
9
|
+
messageId: string | null;
|
|
10
|
+
inReplyTo: string[];
|
|
11
|
+
references: string[];
|
|
12
|
+
}
|
|
13
|
+
interface ReceivedEmail {
|
|
14
|
+
id: string;
|
|
15
|
+
eventId: string;
|
|
16
|
+
receivedAt: string;
|
|
17
|
+
sender: ReceivedEmailAddress;
|
|
18
|
+
replyTarget: ReceivedEmailAddress;
|
|
19
|
+
receivedBy: string;
|
|
20
|
+
receivedByAll: string[];
|
|
21
|
+
subject: string | null;
|
|
22
|
+
replySubject: string;
|
|
23
|
+
forwardSubject: string;
|
|
24
|
+
text: string | null;
|
|
25
|
+
thread: ReceivedEmailThread;
|
|
26
|
+
attachments: WebhookAttachment[];
|
|
27
|
+
auth: EmailAuth;
|
|
28
|
+
analysis: EmailAnalysis;
|
|
29
|
+
raw: EmailReceivedEvent;
|
|
30
|
+
}
|
|
31
|
+
declare function normalizeReceivedEmail(event: EmailReceivedEvent): ReceivedEmail;
|
|
32
|
+
declare function buildReplySubject(subject: string | null | undefined): string;
|
|
33
|
+
declare function buildForwardSubject(subject: string | null | undefined): string;
|
|
34
|
+
declare function formatAddress(address: ReceivedEmailAddress): string;
|
|
35
|
+
declare function parseHeaderAddress(value: string | null | undefined): ReceivedEmailAddress | null;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { buildReplySubject as a, parseHeaderAddress as c, buildForwardSubject as i, ReceivedEmailAddress as n, formatAddress as o, ReceivedEmailThread as r, normalizeReceivedEmail as s, ReceivedEmail as t };
|
|
@@ -17,13 +17,6 @@
|
|
|
17
17
|
* via the `definition` "ForwardResult".
|
|
18
18
|
*/
|
|
19
19
|
type ForwardResult$1 = (ForwardResultInline$1 | ForwardResultAttachmentAnalyzed$1 | ForwardResultAttachmentSkipped$1);
|
|
20
|
-
/**
|
|
21
|
-
* Valid webhook version format (YYYY-MM-DD date string). The SDK accepts any valid date-formatted version, not just the current one, for forward and backward compatibility.
|
|
22
|
-
*
|
|
23
|
-
* This interface was referenced by `EmailReceivedEvent`'s JSON-Schema
|
|
24
|
-
* via the `definition` "WebhookVersion".
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
20
|
/**
|
|
28
21
|
* Webhook payload for the `email.received` event.
|
|
29
22
|
*
|
|
@@ -693,17 +686,9 @@ interface DkimSignature$1 {
|
|
|
693
686
|
* Optional in self-hosted environments.
|
|
694
687
|
*/
|
|
695
688
|
algo: (string | null);
|
|
696
|
-
}
|
|
689
|
+
}
|
|
690
|
+
//#endregion
|
|
697
691
|
//#region src/types.d.ts
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* Webhook payload for the `email.received` event.
|
|
701
|
-
*
|
|
702
|
-
* This is delivered to your webhook endpoint when Primitive receives an email matching your domain configuration.
|
|
703
|
-
*
|
|
704
|
-
* This interface was referenced by `EmailReceivedEvent`'s JSON-Schema
|
|
705
|
-
* via the `definition` "EmailReceivedEvent".
|
|
706
|
-
*/
|
|
707
692
|
type EmailReceivedEvent = EmailReceivedEvent$1;
|
|
708
693
|
type EventType = EmailReceivedEvent["event"];
|
|
709
694
|
declare const EventType: {
|
|
@@ -814,6 +799,5 @@ interface UnknownEvent {
|
|
|
814
799
|
}
|
|
815
800
|
type KnownWebhookEvent = EmailReceivedEvent;
|
|
816
801
|
type WebhookEvent = KnownWebhookEvent | UnknownEvent;
|
|
817
|
-
|
|
818
802
|
//#endregion
|
|
819
|
-
export {
|
|
803
|
+
export { UnknownEvent as A, ParsedDataFailed as C, RawContentDownloadOnly as D, RawContent as E, WebhookAttachment as M, WebhookEvent as N, RawContentInline as O, ParsedDataComplete as S, ParsedStatus as T, ForwardResultInline as _, DmarcPolicy as a, KnownWebhookEvent as b, EmailAnalysis as c, EventType as d, ForwardAnalysis as f, ForwardResultAttachmentSkipped as g, ForwardResultAttachmentAnalyzed as h, DkimSignature as i, ValidateEmailAuthResult as j, SpfResult as k, EmailAuth as l, ForwardResult as m, AuthVerdict as n, DmarcResult as o, ForwardOriginalSender as p, DkimResult as r, EmailAddress as s, AuthConfidence as t, EmailReceivedEvent as u, ForwardVerdict as v, ParsedError as w, ParsedData as x, ForwardVerification as y };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitive webhook event types derived from the canonical JSON schema.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export const EventType = {
|
|
7
|
+
EmailReceived: "email.received",
|
|
8
|
+
};
|
|
9
|
+
export const ParsedStatus = {
|
|
10
|
+
Complete: "complete",
|
|
11
|
+
Failed: "failed",
|
|
12
|
+
};
|
|
13
|
+
export const ForwardVerdict = {
|
|
14
|
+
Legit: "legit",
|
|
15
|
+
Unknown: "unknown",
|
|
16
|
+
};
|
|
17
|
+
export const SpfResult = {
|
|
18
|
+
Pass: "pass",
|
|
19
|
+
Fail: "fail",
|
|
20
|
+
Softfail: "softfail",
|
|
21
|
+
Neutral: "neutral",
|
|
22
|
+
None: "none",
|
|
23
|
+
Temperror: "temperror",
|
|
24
|
+
Permerror: "permerror",
|
|
25
|
+
};
|
|
26
|
+
export const DmarcResult = {
|
|
27
|
+
Pass: "pass",
|
|
28
|
+
Fail: "fail",
|
|
29
|
+
None: "none",
|
|
30
|
+
Temperror: "temperror",
|
|
31
|
+
Permerror: "permerror",
|
|
32
|
+
};
|
|
33
|
+
export const DmarcPolicy = {
|
|
34
|
+
Reject: "reject",
|
|
35
|
+
Quarantine: "quarantine",
|
|
36
|
+
None: "none",
|
|
37
|
+
};
|
|
38
|
+
export const DkimResult = {
|
|
39
|
+
Pass: "pass",
|
|
40
|
+
Fail: "fail",
|
|
41
|
+
Temperror: "temperror",
|
|
42
|
+
Permerror: "permerror",
|
|
43
|
+
};
|
|
44
|
+
export const AuthConfidence = {
|
|
45
|
+
High: "high",
|
|
46
|
+
Medium: "medium",
|
|
47
|
+
Low: "low",
|
|
48
|
+
};
|
|
49
|
+
export const AuthVerdict = {
|
|
50
|
+
Legit: "legit",
|
|
51
|
+
Suspicious: "suspicious",
|
|
52
|
+
Unknown: "unknown",
|
|
53
|
+
};
|
package/dist/webhook/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { A as UnknownEvent, C as ParsedDataFailed, D as RawContentDownloadOnly, E as RawContent, M as WebhookAttachment, N as WebhookEvent, O as RawContentInline, S as ParsedDataComplete, T as ParsedStatus, _ as ForwardResultInline, a as DmarcPolicy, b as KnownWebhookEvent, c as EmailAnalysis, d as EventType, f as ForwardAnalysis, g as ForwardResultAttachmentSkipped, h as ForwardResultAttachmentAnalyzed, i as DkimSignature, j as ValidateEmailAuthResult, k as SpfResult, l as EmailAuth, m as ForwardResult, n as AuthVerdict, o as DmarcResult, p as ForwardOriginalSender, r as DkimResult, s as EmailAddress, t as AuthConfidence, u as EmailReceivedEvent, v as ForwardVerdict, w as ParsedError, x as ParsedData, y as ForwardVerification } from "../types-9vXGZjPd.js";
|
|
2
|
+
import { a as buildReplySubject, c as parseHeaderAddress, i as buildForwardSubject, n as ReceivedEmailAddress, o as formatAddress, r as ReceivedEmailThread, s as normalizeReceivedEmail, t as ReceivedEmail } from "../received-email-DNjpq_Wt.js";
|
|
3
|
+
import { A as VerifyOptions, B as PAYLOAD_ERRORS, C as signStandardWebhooksPayload, D as PRIMITIVE_CONFIRMED_HEADER, E as LEGACY_SIGNATURE_HEADER, F as VerifyDownloadTokenResult, G as VERIFICATION_ERRORS, H as RAW_EMAIL_ERRORS, I as generateDownloadToken, J as WebhookPayloadErrorCode, K as WebhookErrorCode, L as verifyDownloadToken, M as verifyWebhookSignature, N as GenerateDownloadTokenOptions, O as PRIMITIVE_SIGNATURE_HEADER, P as VerifyDownloadTokenOptions, Q as WebhookVerificationErrorCode, R as safeValidateEmailReceivedEvent, S as StandardWebhooksVerifyOptions, T as LEGACY_CONFIRMED_HEADER, U as RawEmailDecodeError, V as PrimitiveWebhookError, W as RawEmailDecodeErrorCode, X as WebhookValidationErrorCode, Y as WebhookValidationError, Z as WebhookVerificationError, _ as emailReceivedEventJsonSchema, a as confirmedHeaders, b as STANDARD_WEBHOOK_TIMESTAMP_HEADER, c as handleWebhook, d as isRawIncluded, f as parseWebhookEvent, g as validateEmailAuth, h as WEBHOOK_VERSION, i as WebhookHeaders, j as signWebhookPayload, k as SignResult, l as isDownloadExpired, m as verifyRawEmailDownload, n as HandleWebhookOptions, o as decodeRawEmail, p as receive, q as WebhookPayloadError, r as ReceiveRequestOptions, s as getDownloadTimeRemaining, t as DecodeRawEmailOptions, u as isEmailReceivedEvent, v as STANDARD_WEBHOOK_ID_HEADER, w as verifyStandardWebhooksSignature, x as StandardWebhooksSignResult, y as STANDARD_WEBHOOK_SIGNATURE_HEADER, z as validateEmailReceivedEvent } from "../index-CbEivn3S.js";
|
|
4
|
+
export { AuthConfidence, AuthVerdict, DecodeRawEmailOptions, DkimResult, DkimSignature, DmarcPolicy, DmarcResult, EmailAddress, EmailAnalysis, EmailAuth, EmailReceivedEvent, EventType, ForwardAnalysis, ForwardOriginalSender, ForwardResult, ForwardResultAttachmentAnalyzed, ForwardResultAttachmentSkipped, ForwardResultInline, ForwardVerdict, ForwardVerification, GenerateDownloadTokenOptions, HandleWebhookOptions, KnownWebhookEvent, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedData, ParsedDataComplete, ParsedDataFailed, ParsedError, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawContent, RawContentDownloadOnly, RawContentInline, RawEmailDecodeError, RawEmailDecodeErrorCode, ReceiveRequestOptions, ReceivedEmail, ReceivedEmailAddress, ReceivedEmailThread, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SignResult, SpfResult, StandardWebhooksSignResult, StandardWebhooksVerifyOptions, UnknownEvent, VERIFICATION_ERRORS, ValidateEmailAuthResult, VerifyDownloadTokenOptions, VerifyDownloadTokenResult, VerifyOptions, WEBHOOK_VERSION, WebhookAttachment, WebhookErrorCode, WebhookEvent, WebhookHeaders, WebhookPayloadError, WebhookPayloadErrorCode, WebhookValidationError, WebhookValidationErrorCode, WebhookVerificationError, WebhookVerificationErrorCode, buildForwardSubject, buildReplySubject, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, formatAddress, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, normalizeReceivedEmail, parseHeaderAddress, parseWebhookEvent, receive, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
|
package/dist/webhook/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export { AuthConfidence, AuthVerdict, DkimResult, DmarcPolicy, DmarcResult, EventType, ForwardVerdict, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawEmailDecodeError, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SpfResult, VERIFICATION_ERRORS, WEBHOOK_VERSION, WebhookPayloadError, WebhookValidationError, WebhookVerificationError, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, parseWebhookEvent, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
|
|
1
|
+
import { a as parseHeaderAddress, i as normalizeReceivedEmail, n as buildReplySubject, r as formatAddress, t as buildForwardSubject } from "../received-email-D6tKtWwW.js";
|
|
2
|
+
import { A as PRIMITIVE_CONFIRMED_HEADER, B as RAW_EMAIL_ERRORS, C as STANDARD_WEBHOOK_ID_HEADER, D as verifyStandardWebhooksSignature, E as signStandardWebhooksPayload, F as verifyDownloadToken, G as WebhookVerificationError, H as VERIFICATION_ERRORS, I as safeValidateEmailReceivedEvent, L as validateEmailReceivedEvent, M as signWebhookPayload, N as verifyWebhookSignature, O as LEGACY_CONFIRMED_HEADER, P as generateDownloadToken, R as PAYLOAD_ERRORS, S as emailReceivedEventJsonSchema, T as STANDARD_WEBHOOK_TIMESTAMP_HEADER, U as WebhookPayloadError, V as RawEmailDecodeError, W as WebhookValidationError, _ as DmarcResult, a as isDownloadExpired, b as ParsedStatus, c as parseWebhookEvent, d as WEBHOOK_VERSION, f as validateEmailAuth, g as DmarcPolicy, h as DkimResult, i as handleWebhook, j as PRIMITIVE_SIGNATURE_HEADER, k as LEGACY_SIGNATURE_HEADER, l as receive, m as AuthVerdict, n as decodeRawEmail, o as isEmailReceivedEvent, p as AuthConfidence, r as getDownloadTimeRemaining, s as isRawIncluded, t as confirmedHeaders, u as verifyRawEmailDownload, v as EventType, w as STANDARD_WEBHOOK_SIGNATURE_HEADER, x as SpfResult, y as ForwardVerdict, z as PrimitiveWebhookError } from "../webhook-zkN4wUTs.js";
|
|
3
|
+
export { AuthConfidence, AuthVerdict, DkimResult, DmarcPolicy, DmarcResult, EventType, ForwardVerdict, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawEmailDecodeError, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SpfResult, VERIFICATION_ERRORS, WEBHOOK_VERSION, WebhookPayloadError, WebhookValidationError, WebhookVerificationError, buildForwardSubject, buildReplySubject, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, formatAddress, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, normalizeReceivedEmail, parseHeaderAddress, parseWebhookEvent, receive, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { parseFromHeaderLoose } from "../parser/address-parser.js";
|
|
2
|
+
const REPLY_PREFIX_RE = /^re\s*:/i;
|
|
3
|
+
const FORWARD_PREFIX_RE = /^(fwd?|fw)\s*:/i;
|
|
4
|
+
export function normalizeReceivedEmail(event) {
|
|
5
|
+
const receivedBy = event.email.smtp.rcpt_to[0];
|
|
6
|
+
if (!receivedBy) {
|
|
7
|
+
throw new Error("email.smtp.rcpt_to must contain at least one recipient");
|
|
8
|
+
}
|
|
9
|
+
const sender = parseHeaderAddress(event.email.headers.from) ?? {
|
|
10
|
+
address: event.email.smtp.mail_from.trim().toLowerCase(),
|
|
11
|
+
name: null,
|
|
12
|
+
};
|
|
13
|
+
const replyTarget = firstStructuredAddress(event.email.parsed.reply_to) ?? sender;
|
|
14
|
+
const subject = event.email.headers.subject ?? null;
|
|
15
|
+
const references = event.email.parsed.references ?? [];
|
|
16
|
+
const messageId = event.email.headers.message_id ?? null;
|
|
17
|
+
return {
|
|
18
|
+
id: event.email.id,
|
|
19
|
+
eventId: event.id,
|
|
20
|
+
receivedAt: event.email.received_at,
|
|
21
|
+
sender,
|
|
22
|
+
replyTarget,
|
|
23
|
+
receivedBy,
|
|
24
|
+
receivedByAll: [...event.email.smtp.rcpt_to],
|
|
25
|
+
subject,
|
|
26
|
+
replySubject: buildReplySubject(subject),
|
|
27
|
+
forwardSubject: buildForwardSubject(subject),
|
|
28
|
+
text: event.email.parsed.body_text ?? null,
|
|
29
|
+
thread: {
|
|
30
|
+
messageId,
|
|
31
|
+
inReplyTo: event.email.parsed.in_reply_to ?? [],
|
|
32
|
+
references,
|
|
33
|
+
},
|
|
34
|
+
attachments: event.email.parsed.attachments ?? [],
|
|
35
|
+
auth: event.email.auth,
|
|
36
|
+
analysis: event.email.analysis,
|
|
37
|
+
raw: event,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function buildReplySubject(subject) {
|
|
41
|
+
const trimmed = subject?.trim() ?? "";
|
|
42
|
+
if (trimmed.length === 0) {
|
|
43
|
+
return "Re:";
|
|
44
|
+
}
|
|
45
|
+
return REPLY_PREFIX_RE.test(trimmed) ? trimmed : `Re: ${trimmed}`;
|
|
46
|
+
}
|
|
47
|
+
export function buildForwardSubject(subject) {
|
|
48
|
+
const trimmed = subject?.trim() ?? "";
|
|
49
|
+
if (trimmed.length === 0) {
|
|
50
|
+
return "Fwd:";
|
|
51
|
+
}
|
|
52
|
+
return FORWARD_PREFIX_RE.test(trimmed) ? trimmed : `Fwd: ${trimmed}`;
|
|
53
|
+
}
|
|
54
|
+
export function formatAddress(address) {
|
|
55
|
+
return address.name
|
|
56
|
+
? `${address.name} <${address.address}>`
|
|
57
|
+
: address.address;
|
|
58
|
+
}
|
|
59
|
+
function firstStructuredAddress(addresses) {
|
|
60
|
+
const address = addresses?.[0];
|
|
61
|
+
if (!address) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
address: address.address.trim().toLowerCase(),
|
|
66
|
+
name: address.name ?? null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Lenient about quirky headers (unquoted commas in display names, missing
|
|
70
|
+
// closing angle brackets) but strict about the resulting address: it must
|
|
71
|
+
// validate as an email, otherwise the normalizer falls back to the SMTP
|
|
72
|
+
// envelope sender. Exported so cross-SDK fixtures can exercise it directly.
|
|
73
|
+
export function parseHeaderAddress(value) {
|
|
74
|
+
const parsed = parseFromHeaderLoose(value);
|
|
75
|
+
if (!parsed) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
address: parsed.address,
|
|
80
|
+
name: parsed.name?.trim() || null,
|
|
81
|
+
};
|
|
82
|
+
}
|