@primitivedotdev/sdk 0.24.0 → 0.25.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/dist/api/generated/sdk.gen.js +29 -17
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.js +6 -0
- package/dist/api/verify-signature.js +198 -0
- package/dist/{api-BjzvA2Fy.js → api-CoP5vKPK.js} +170 -19
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/errors-C53fe686.d.ts +245 -0
- package/dist/errors-x91I_yEt.js +287 -0
- package/dist/{index-CDlwyxdp.d.ts → index-Dbx9udpX.d.ts} +2 -210
- package/dist/{index-QTYQpSFt.d.ts → index-DdITDPea.d.ts} +105 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/openapi/openapi.generated.js +21 -1
- package/dist/openapi/operations.generated.js +15 -3
- package/dist/webhook/errors.js +224 -0
- package/dist/webhook/index.d.ts +2 -2
- package/dist/webhook/index.js +2 -2
- package/dist/{webhook-rUjGV6Zu.js → webhook-DJkfUnFZ.js} +2 -220
- package/oclif.manifest.json +23 -2
- package/package.json +1 -1
- package/dist/received-email-D6tKtWwW.js +0 -69
- package/dist/received-email-DNjpq_Wt.d.ts +0 -37
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-pbuEa-1d.js";
|
|
2
|
-
import {
|
|
2
|
+
import { c as WebhookVerificationError, d as formatAddress } from "./errors-x91I_yEt.js";
|
|
3
3
|
//#region src/api/generated/core/bodySerializer.gen.ts
|
|
4
4
|
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
5
5
|
//#endregion
|
|
@@ -689,7 +689,7 @@ const pollCliLogin = (options) => (options.client ?? client$1).post({
|
|
|
689
689
|
url: "/cli/login/poll",
|
|
690
690
|
...options,
|
|
691
691
|
headers: {
|
|
692
|
-
"Content-Type": "application/json",
|
|
692
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
693
693
|
...options.headers
|
|
694
694
|
}
|
|
695
695
|
});
|
|
@@ -734,7 +734,7 @@ const updateAccount = (options) => (options.client ?? client$1).patch({
|
|
|
734
734
|
url: "/account",
|
|
735
735
|
...options,
|
|
736
736
|
headers: {
|
|
737
|
-
"Content-Type": "application/json",
|
|
737
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
738
738
|
...options.headers
|
|
739
739
|
}
|
|
740
740
|
});
|
|
@@ -828,7 +828,7 @@ const addDomain = (options) => (options.client ?? client$1).post({
|
|
|
828
828
|
url: "/domains",
|
|
829
829
|
...options,
|
|
830
830
|
headers: {
|
|
831
|
-
"Content-Type": "application/json",
|
|
831
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
832
832
|
...options.headers
|
|
833
833
|
}
|
|
834
834
|
});
|
|
@@ -860,7 +860,7 @@ const updateDomain = (options) => (options.client ?? client$1).patch({
|
|
|
860
860
|
url: "/domains/{id}",
|
|
861
861
|
...options,
|
|
862
862
|
headers: {
|
|
863
|
-
"Content-Type": "application/json",
|
|
863
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
864
864
|
...options.headers
|
|
865
865
|
}
|
|
866
866
|
});
|
|
@@ -1042,7 +1042,7 @@ const replyToEmail = (options) => (options.client ?? client$1).post({
|
|
|
1042
1042
|
url: "/emails/{id}/reply",
|
|
1043
1043
|
...options,
|
|
1044
1044
|
headers: {
|
|
1045
|
-
"Content-Type": "application/json",
|
|
1045
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1046
1046
|
...options.headers
|
|
1047
1047
|
}
|
|
1048
1048
|
});
|
|
@@ -1132,7 +1132,7 @@ const createEndpoint = (options) => (options.client ?? client$1).post({
|
|
|
1132
1132
|
url: "/endpoints",
|
|
1133
1133
|
...options,
|
|
1134
1134
|
headers: {
|
|
1135
|
-
"Content-Type": "application/json",
|
|
1135
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1136
1136
|
...options.headers
|
|
1137
1137
|
}
|
|
1138
1138
|
});
|
|
@@ -1167,7 +1167,7 @@ const updateEndpoint = (options) => (options.client ?? client$1).patch({
|
|
|
1167
1167
|
url: "/endpoints/{id}",
|
|
1168
1168
|
...options,
|
|
1169
1169
|
headers: {
|
|
1170
|
-
"Content-Type": "application/json",
|
|
1170
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1171
1171
|
...options.headers
|
|
1172
1172
|
}
|
|
1173
1173
|
});
|
|
@@ -1217,7 +1217,7 @@ const createFilter = (options) => (options.client ?? client$1).post({
|
|
|
1217
1217
|
url: "/filters",
|
|
1218
1218
|
...options,
|
|
1219
1219
|
headers: {
|
|
1220
|
-
"Content-Type": "application/json",
|
|
1220
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1221
1221
|
...options.headers
|
|
1222
1222
|
}
|
|
1223
1223
|
});
|
|
@@ -1245,7 +1245,7 @@ const updateFilter = (options) => (options.client ?? client$1).patch({
|
|
|
1245
1245
|
url: "/filters/{id}",
|
|
1246
1246
|
...options,
|
|
1247
1247
|
headers: {
|
|
1248
|
-
"Content-Type": "application/json",
|
|
1248
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1249
1249
|
...options.headers
|
|
1250
1250
|
}
|
|
1251
1251
|
});
|
|
@@ -1350,7 +1350,7 @@ const sendEmail = (options) => (options.client ?? client$1).post({
|
|
|
1350
1350
|
url: "/send-mail",
|
|
1351
1351
|
...options,
|
|
1352
1352
|
headers: {
|
|
1353
|
-
"Content-Type": "application/json",
|
|
1353
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1354
1354
|
...options.headers
|
|
1355
1355
|
}
|
|
1356
1356
|
});
|
|
@@ -1455,7 +1455,7 @@ const createFunction = (options) => (options.client ?? client$1).post({
|
|
|
1455
1455
|
url: "/functions",
|
|
1456
1456
|
...options,
|
|
1457
1457
|
headers: {
|
|
1458
|
-
"Content-Type": "application/json",
|
|
1458
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1459
1459
|
...options.headers
|
|
1460
1460
|
}
|
|
1461
1461
|
});
|
|
@@ -1520,7 +1520,7 @@ const updateFunction = (options) => (options.client ?? client$1).put({
|
|
|
1520
1520
|
url: "/functions/{id}",
|
|
1521
1521
|
...options,
|
|
1522
1522
|
headers: {
|
|
1523
|
-
"Content-Type": "application/json",
|
|
1523
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1524
1524
|
...options.headers
|
|
1525
1525
|
}
|
|
1526
1526
|
});
|
|
@@ -1528,8 +1528,14 @@ const updateFunction = (options) => (options.client ?? client$1).put({
|
|
|
1528
1528
|
* Send a test invocation
|
|
1529
1529
|
*
|
|
1530
1530
|
* Sends a real test email from a Primitive-controlled sender to a
|
|
1531
|
-
*
|
|
1532
|
-
*
|
|
1531
|
+
* local-part on one of the org's verified inbound domains. By
|
|
1532
|
+
* default the recipient is a synthetic
|
|
1533
|
+
* `__primitive_function_test+<random>@<domain>` address that
|
|
1534
|
+
* every handler's catch-all routing receives identically; pass
|
|
1535
|
+
* `local_part` to override and exercise routing logic that
|
|
1536
|
+
* branches on a specific recipient (the common pattern when one
|
|
1537
|
+
* function handles multiple inboxes like `summarize@` and
|
|
1538
|
+
* `action@`). The function fires through the normal MX delivery
|
|
1533
1539
|
* path, so reply / send-mail calls from inside the handler
|
|
1534
1540
|
* against the inbound's `email.id` work the same as in
|
|
1535
1541
|
* production. Returns immediately after the send is queued; the
|
|
@@ -1539,6 +1545,8 @@ const updateFunction = (options) => (options.client ?? client$1).put({
|
|
|
1539
1545
|
* Requires that the function is currently `deployed`. Returns 422
|
|
1540
1546
|
* if the function is in `pending` or `failed` state, or if the
|
|
1541
1547
|
* org has no verified inbound domain to receive the test mail.
|
|
1548
|
+
* Returns 400 if `local_part` is set to a value that does not
|
|
1549
|
+
* match the local-part character set.
|
|
1542
1550
|
*
|
|
1543
1551
|
*/
|
|
1544
1552
|
const testFunction = (options) => (options.client ?? client$1).post({
|
|
@@ -1547,7 +1555,11 @@ const testFunction = (options) => (options.client ?? client$1).post({
|
|
|
1547
1555
|
type: "http"
|
|
1548
1556
|
}],
|
|
1549
1557
|
url: "/functions/{id}/test",
|
|
1550
|
-
...options
|
|
1558
|
+
...options,
|
|
1559
|
+
headers: {
|
|
1560
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1561
|
+
...options.headers
|
|
1562
|
+
}
|
|
1551
1563
|
});
|
|
1552
1564
|
/**
|
|
1553
1565
|
* List a function's secrets
|
|
@@ -1594,7 +1606,7 @@ const createFunctionSecret = (options) => (options.client ?? client$1).post({
|
|
|
1594
1606
|
url: "/functions/{id}/secrets",
|
|
1595
1607
|
...options,
|
|
1596
1608
|
headers: {
|
|
1597
|
-
"Content-Type": "application/json",
|
|
1609
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1598
1610
|
...options.headers
|
|
1599
1611
|
}
|
|
1600
1612
|
});
|
|
@@ -1634,11 +1646,150 @@ const setFunctionSecret = (options) => (options.client ?? client$1).put({
|
|
|
1634
1646
|
url: "/functions/{id}/secrets/{key}",
|
|
1635
1647
|
...options,
|
|
1636
1648
|
headers: {
|
|
1637
|
-
"Content-Type": "application/json",
|
|
1649
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1638
1650
|
...options.headers
|
|
1639
1651
|
}
|
|
1640
1652
|
});
|
|
1641
1653
|
//#endregion
|
|
1654
|
+
//#region src/api/verify-signature.ts
|
|
1655
|
+
/**
|
|
1656
|
+
* Workers-safe webhook signature verification.
|
|
1657
|
+
*
|
|
1658
|
+
* Mirrors `verifyWebhookSignature` from `@primitivedotdev/sdk` but
|
|
1659
|
+
* implements the HMAC-SHA256 step with the Web Crypto API
|
|
1660
|
+
* (`crypto.subtle`) instead of `node:crypto`. The Node version is
|
|
1661
|
+
* still the right choice for server-side handlers running on Node
|
|
1662
|
+
* (it's measurably faster and supports Buffer bodies); this one
|
|
1663
|
+
* exists so a Primitive Function handler can bundle the verifier
|
|
1664
|
+
* without dragging in a `node:crypto` polyfill that inflates the
|
|
1665
|
+
* deploy artifact past the size cap.
|
|
1666
|
+
*
|
|
1667
|
+
* Available natively in Workers, Node 22+, browsers, Deno, and Bun.
|
|
1668
|
+
* Zero polyfill weight, zero new runtime dependencies.
|
|
1669
|
+
*
|
|
1670
|
+
* Surface contract matches the Node verifier exactly: same input
|
|
1671
|
+
* shape, same `WebhookVerificationError` class, same set of error
|
|
1672
|
+
* codes. Existing callers can swap the import path with no other
|
|
1673
|
+
* code changes:
|
|
1674
|
+
*
|
|
1675
|
+
* // Node (existing):
|
|
1676
|
+
* import { verifyWebhookSignature } from '@primitivedotdev/sdk';
|
|
1677
|
+
*
|
|
1678
|
+
* // Workers / in-handler (this file):
|
|
1679
|
+
* import { verifyWebhookSignature } from '@primitivedotdev/sdk/api';
|
|
1680
|
+
*/
|
|
1681
|
+
const PRIMITIVE_SIGNATURE_HEADER = "Primitive-Signature";
|
|
1682
|
+
const DEFAULT_TOLERANCE_SECONDS = 300;
|
|
1683
|
+
const FUTURE_TOLERANCE_SECONDS = 60;
|
|
1684
|
+
const HEX_PATTERN = /^[0-9a-f]+$/i;
|
|
1685
|
+
const HEX_LENGTH = 64;
|
|
1686
|
+
const UNIX_SECONDS_PATTERN = /^\d{1,10}$/;
|
|
1687
|
+
function parseSignatureHeader(signatureHeader) {
|
|
1688
|
+
if (!signatureHeader || typeof signatureHeader !== "string") return null;
|
|
1689
|
+
const parts = signatureHeader.split(",");
|
|
1690
|
+
let timestamp = null;
|
|
1691
|
+
const signatures = [];
|
|
1692
|
+
for (const part of parts) {
|
|
1693
|
+
const idx = part.indexOf("=");
|
|
1694
|
+
if (idx === -1) continue;
|
|
1695
|
+
const key = part.slice(0, idx).trim();
|
|
1696
|
+
const value = part.slice(idx + 1).trim();
|
|
1697
|
+
if (!key || !value) continue;
|
|
1698
|
+
if (key === "t") {
|
|
1699
|
+
if (!UNIX_SECONDS_PATTERN.test(value)) continue;
|
|
1700
|
+
const parsed = Number(value);
|
|
1701
|
+
if (Number.isSafeInteger(parsed)) timestamp = parsed;
|
|
1702
|
+
} else if (key === "v1") signatures.push(value);
|
|
1703
|
+
}
|
|
1704
|
+
if (timestamp === null || signatures.length === 0) return null;
|
|
1705
|
+
return {
|
|
1706
|
+
timestamp,
|
|
1707
|
+
signatures
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function isValidHex(str) {
|
|
1711
|
+
return str.length === HEX_LENGTH && HEX_PATTERN.test(str);
|
|
1712
|
+
}
|
|
1713
|
+
function arrayBufferToHex(buffer) {
|
|
1714
|
+
const bytes = new Uint8Array(buffer);
|
|
1715
|
+
let hex = "";
|
|
1716
|
+
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, "0");
|
|
1717
|
+
return hex;
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Constant-time comparison of two equal-length hex strings. Returns
|
|
1721
|
+
* false if lengths differ (intentionally not a security issue: lengths
|
|
1722
|
+
* are public). Iterates the full length regardless of mismatch so the
|
|
1723
|
+
* timing signal does not reveal the position of the first divergence.
|
|
1724
|
+
*/
|
|
1725
|
+
function timingSafeEqualHex(a, b) {
|
|
1726
|
+
if (a.length !== b.length) return false;
|
|
1727
|
+
let diff = 0;
|
|
1728
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1729
|
+
return diff === 0;
|
|
1730
|
+
}
|
|
1731
|
+
async function computeHmacHex(secret, payload) {
|
|
1732
|
+
const encoder = new TextEncoder();
|
|
1733
|
+
const keyData = encoder.encode(secret);
|
|
1734
|
+
const key = await crypto.subtle.importKey("raw", keyData, {
|
|
1735
|
+
name: "HMAC",
|
|
1736
|
+
hash: "SHA-256"
|
|
1737
|
+
}, false, ["sign"]);
|
|
1738
|
+
return arrayBufferToHex(await crypto.subtle.sign("HMAC", key, encoder.encode(payload)));
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Verify a webhook signature using the Web Crypto API.
|
|
1742
|
+
*
|
|
1743
|
+
* Throws `WebhookVerificationError` on failure with a specific error
|
|
1744
|
+
* code matching the Node verifier's set. Returns `true` on success.
|
|
1745
|
+
*
|
|
1746
|
+
* @example
|
|
1747
|
+
* ```typescript
|
|
1748
|
+
* import {
|
|
1749
|
+
* verifyWebhookSignature,
|
|
1750
|
+
* WebhookVerificationError,
|
|
1751
|
+
* PRIMITIVE_SIGNATURE_HEADER,
|
|
1752
|
+
* } from '@primitivedotdev/sdk/api';
|
|
1753
|
+
*
|
|
1754
|
+
* export default {
|
|
1755
|
+
* async fetch(request: Request, env: { PRIMITIVE_WEBHOOK_SECRET: string }) {
|
|
1756
|
+
* const rawBody = await request.text();
|
|
1757
|
+
* try {
|
|
1758
|
+
* await verifyWebhookSignature({
|
|
1759
|
+
* rawBody,
|
|
1760
|
+
* signatureHeader: request.headers.get(PRIMITIVE_SIGNATURE_HEADER) ?? '',
|
|
1761
|
+
* secret: env.PRIMITIVE_WEBHOOK_SECRET,
|
|
1762
|
+
* });
|
|
1763
|
+
* } catch (err) {
|
|
1764
|
+
* if (err instanceof WebhookVerificationError) {
|
|
1765
|
+
* return new Response('invalid signature', { status: 401 });
|
|
1766
|
+
* }
|
|
1767
|
+
* throw err;
|
|
1768
|
+
* }
|
|
1769
|
+
* // ... process the webhook
|
|
1770
|
+
* },
|
|
1771
|
+
* };
|
|
1772
|
+
* ```
|
|
1773
|
+
*/
|
|
1774
|
+
async function verifyWebhookSignature(opts) {
|
|
1775
|
+
const { rawBody, signatureHeader, secret, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS, nowSeconds } = opts;
|
|
1776
|
+
if (!secret) throw new WebhookVerificationError("MISSING_SECRET", "Webhook secret is required but was empty or not provided");
|
|
1777
|
+
const parsed = parseSignatureHeader(signatureHeader);
|
|
1778
|
+
if (!parsed) throw new WebhookVerificationError("INVALID_SIGNATURE_HEADER", "Invalid Primitive-Signature header format. Expected: t={timestamp},v1={signature}");
|
|
1779
|
+
const { timestamp, signatures } = parsed;
|
|
1780
|
+
const age = (nowSeconds ?? Math.floor(Date.now() / 1e3)) - timestamp;
|
|
1781
|
+
if (age > toleranceSeconds) throw new WebhookVerificationError("TIMESTAMP_OUT_OF_RANGE", `Webhook timestamp too old (${age}s). Max age is ${toleranceSeconds}s.`);
|
|
1782
|
+
if (age < -FUTURE_TOLERANCE_SECONDS) throw new WebhookVerificationError("TIMESTAMP_OUT_OF_RANGE", "Webhook timestamp is too far in the future. Check server clock sync.");
|
|
1783
|
+
const expectedHex = await computeHmacHex(secret, `${timestamp}.${rawBody}`);
|
|
1784
|
+
let anyMatch = false;
|
|
1785
|
+
for (const candidate of signatures) {
|
|
1786
|
+
if (!isValidHex(candidate)) continue;
|
|
1787
|
+
if (timingSafeEqualHex(candidate.toLowerCase(), expectedHex)) anyMatch = true;
|
|
1788
|
+
}
|
|
1789
|
+
if (!anyMatch) throw new WebhookVerificationError("SIGNATURE_MISMATCH", "Webhook signature did not match. The body may have been modified in transit, or the secret may be out of date.");
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
//#endregion
|
|
1642
1793
|
//#region src/api/index.ts
|
|
1643
1794
|
const DEFAULT_API_BASE_URL_1 = "https://www.primitive.dev/api/v1";
|
|
1644
1795
|
const DEFAULT_API_BASE_URL_2 = "https://api.primitive.dev/v1";
|
|
@@ -1927,4 +2078,4 @@ function client(options = {}) {
|
|
|
1927
2078
|
}
|
|
1928
2079
|
const operations = sdk_gen_exports;
|
|
1929
2080
|
//#endregion
|
|
1930
|
-
export {
|
|
2081
|
+
export { updateAccount as $, getSendPermissions as A, listFunctions as B, deleteFunctionSecret as C, getAccount as D, downloadRawEmail as E, listDomains as F, replyToEmail as G, pollCliLogin as H, listEmails as I, sendEmail as J, rotateWebhookSecret as K, listEndpoints as L, getStorageStats as M, getWebhookSecret as N, getEmail as O, listDeliveries as P, testFunction as Q, listFilters as R, deleteFunction as S, downloadAttachments as T, replayDelivery as U, listSentEmails as V, replayEmailWebhooks as W, startCliLogin as X, setFunctionSecret as Y, testEndpoint as Z, createFunctionSecret as _, PrimitiveClient as a, deleteEndpoint as b, createPrimitiveClient as c, verifyWebhookSignature as d, updateDomain as et, addDomain as f, createFunction as g, createFilter as h, PrimitiveApiError as i, verifyDomain as it, getSentEmail as j, getFunction as k, operations as l, createEndpoint as m, DEFAULT_API_BASE_URL_2 as n, updateFilter as nt, client as o, cliLogout as p, searchEmails as q, PrimitiveApiClient as r, updateFunction as rt, createPrimitiveApiClient as s, DEFAULT_API_BASE_URL_1 as t, updateEndpoint as tt, PRIMITIVE_SIGNATURE_HEADER as u, deleteDomain as v, discardEmailContent as w, deleteFilter as x, deleteEmail as y, listFunctionSecrets as z };
|
package/dist/contract/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as ParsedDataFailed, D as RawContentDownloadOnly, M as WebhookAttachment, O as RawContentInline, S as ParsedDataComplete, c as EmailAnalysis, l as EmailAuth, s as EmailAddress, u as EmailReceivedEvent, w as ParsedError } from "../types-9vXGZjPd.js";
|
|
2
|
-
import { C as signStandardWebhooksPayload, h as WEBHOOK_VERSION, j as signWebhookPayload, k as SignResult, x as StandardWebhooksSignResult } from "../index-
|
|
2
|
+
import { C as signStandardWebhooksPayload, h as WEBHOOK_VERSION, j as signWebhookPayload, k as SignResult, x as StandardWebhooksSignResult } from "../index-Dbx9udpX.js";
|
|
3
3
|
|
|
4
4
|
//#region src/contract/contract.d.ts
|
|
5
5
|
/** Maximum raw email size for inline inclusion (256 KB). */
|
package/dist/contract/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as signStandardWebhooksPayload, L as validateEmailReceivedEvent, M as signWebhookPayload, d as WEBHOOK_VERSION } from "../webhook-
|
|
1
|
+
import { E as signStandardWebhooksPayload, L as validateEmailReceivedEvent, M as signWebhookPayload, d as WEBHOOK_VERSION } from "../webhook-DJkfUnFZ.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
//#region src/contract/contract.ts
|
|
4
4
|
/**
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { M as WebhookAttachment, c as EmailAnalysis, l as EmailAuth, u as EmailReceivedEvent } from "./types-9vXGZjPd.js";
|
|
2
|
+
import { ErrorObject } from "ajv";
|
|
3
|
+
|
|
4
|
+
//#region src/webhook/received-email.d.ts
|
|
5
|
+
interface ReceivedEmailAddress {
|
|
6
|
+
address: string;
|
|
7
|
+
name: string | null;
|
|
8
|
+
}
|
|
9
|
+
interface ReceivedEmailThread {
|
|
10
|
+
messageId: string | null;
|
|
11
|
+
inReplyTo: string[];
|
|
12
|
+
references: string[];
|
|
13
|
+
}
|
|
14
|
+
interface ReceivedEmail {
|
|
15
|
+
id: string;
|
|
16
|
+
eventId: string;
|
|
17
|
+
receivedAt: string;
|
|
18
|
+
sender: ReceivedEmailAddress;
|
|
19
|
+
replyTarget: ReceivedEmailAddress;
|
|
20
|
+
receivedBy: string;
|
|
21
|
+
receivedByAll: string[];
|
|
22
|
+
subject: string | null;
|
|
23
|
+
replySubject: string;
|
|
24
|
+
forwardSubject: string;
|
|
25
|
+
text: string | null;
|
|
26
|
+
thread: ReceivedEmailThread;
|
|
27
|
+
attachments: WebhookAttachment[];
|
|
28
|
+
auth: EmailAuth;
|
|
29
|
+
analysis: EmailAnalysis;
|
|
30
|
+
raw: EmailReceivedEvent;
|
|
31
|
+
}
|
|
32
|
+
declare function normalizeReceivedEmail(event: EmailReceivedEvent): ReceivedEmail;
|
|
33
|
+
declare function buildReplySubject(subject: string | null | undefined): string;
|
|
34
|
+
declare function buildForwardSubject(subject: string | null | undefined): string;
|
|
35
|
+
declare function formatAddress(address: ReceivedEmailAddress): string;
|
|
36
|
+
declare function parseHeaderAddress(value: string | null | undefined): ReceivedEmailAddress | null;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/webhook/errors.d.ts
|
|
39
|
+
/**
|
|
40
|
+
* Verification error definitions.
|
|
41
|
+
* Use these for documentation, dashboards, and i18n.
|
|
42
|
+
*/
|
|
43
|
+
declare const VERIFICATION_ERRORS: {
|
|
44
|
+
readonly INVALID_SIGNATURE_HEADER: {
|
|
45
|
+
readonly message: "Missing or malformed Primitive-Signature header";
|
|
46
|
+
readonly suggestion: "Check that you're reading the correct header (Primitive-Signature) and it's being passed correctly from your web framework.";
|
|
47
|
+
};
|
|
48
|
+
readonly TIMESTAMP_OUT_OF_RANGE: {
|
|
49
|
+
readonly message: "Timestamp is too old (possible replay attack)";
|
|
50
|
+
readonly suggestion: "This could indicate a replay attack, network delay, or server clock drift. Check your server's time is synced.";
|
|
51
|
+
};
|
|
52
|
+
readonly SIGNATURE_MISMATCH: {
|
|
53
|
+
readonly message: "Signature doesn't match expected value";
|
|
54
|
+
readonly suggestion: "Verify the webhook secret matches and you're using the raw request body (not re-serialized JSON).";
|
|
55
|
+
};
|
|
56
|
+
readonly MISSING_SECRET: {
|
|
57
|
+
readonly message: "No webhook secret was provided";
|
|
58
|
+
readonly suggestion: "Pass your webhook secret from the Primitive dashboard. Check that the environment variable is set.";
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Payload parsing error definitions.
|
|
63
|
+
* Use these for documentation, dashboards, and i18n.
|
|
64
|
+
*/
|
|
65
|
+
declare const PAYLOAD_ERRORS: {
|
|
66
|
+
readonly PAYLOAD_NULL: {
|
|
67
|
+
readonly message: "Webhook payload is null";
|
|
68
|
+
readonly suggestion: "Ensure you're passing the parsed JSON body, not null. Check your framework's body parsing middleware.";
|
|
69
|
+
};
|
|
70
|
+
readonly PAYLOAD_UNDEFINED: {
|
|
71
|
+
readonly message: "Webhook payload is undefined";
|
|
72
|
+
readonly suggestion: "The payload was not provided. Make sure you're passing the request body to the handler.";
|
|
73
|
+
};
|
|
74
|
+
readonly PAYLOAD_WRONG_TYPE: {
|
|
75
|
+
readonly message: "Webhook payload must be an object";
|
|
76
|
+
readonly suggestion: "The payload should be a parsed JSON object. Check that you're not passing a string or other primitive.";
|
|
77
|
+
};
|
|
78
|
+
readonly PAYLOAD_IS_ARRAY: {
|
|
79
|
+
readonly message: "Webhook payload is an array, expected object";
|
|
80
|
+
readonly suggestion: "Primitive webhooks are single event objects, not arrays. Check the payload structure.";
|
|
81
|
+
};
|
|
82
|
+
readonly PAYLOAD_MISSING_EVENT: {
|
|
83
|
+
readonly message: "Webhook payload missing 'event' field";
|
|
84
|
+
readonly suggestion: "All webhook payloads must have an 'event' field. This may not be a valid Primitive webhook.";
|
|
85
|
+
};
|
|
86
|
+
readonly PAYLOAD_UNKNOWN_EVENT: {
|
|
87
|
+
readonly message: "Unknown webhook event type";
|
|
88
|
+
readonly suggestion: "This event type is not recognized. You may need to update your SDK or handle unknown events gracefully.";
|
|
89
|
+
};
|
|
90
|
+
readonly PAYLOAD_EMPTY_BODY: {
|
|
91
|
+
readonly message: "Request body is empty";
|
|
92
|
+
readonly suggestion: "The request body was empty. Ensure the webhook is sending data and your framework is parsing it correctly.";
|
|
93
|
+
};
|
|
94
|
+
readonly JSON_PARSE_FAILED: {
|
|
95
|
+
readonly message: "Failed to parse JSON body";
|
|
96
|
+
readonly suggestion: "The request body is not valid JSON. Check the raw body content and Content-Type header.";
|
|
97
|
+
};
|
|
98
|
+
readonly INVALID_ENCODING: {
|
|
99
|
+
readonly message: "Invalid body encoding";
|
|
100
|
+
readonly suggestion: "The request body encoding is not supported. Primitive webhooks use UTF-8 encoded JSON.";
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Raw email decode error definitions.
|
|
105
|
+
* Use these for documentation, dashboards, and i18n.
|
|
106
|
+
*/
|
|
107
|
+
declare const RAW_EMAIL_ERRORS: {
|
|
108
|
+
readonly NOT_INCLUDED: {
|
|
109
|
+
readonly message: "Raw email content not included inline";
|
|
110
|
+
readonly suggestion: "Use the download URL at event.email.content.download.url to fetch the raw email.";
|
|
111
|
+
};
|
|
112
|
+
readonly INVALID_BASE64: {
|
|
113
|
+
readonly message: "Raw email content is not valid base64";
|
|
114
|
+
readonly suggestion: "The raw email data is malformed. Fetch the raw email from the download URL or regenerate the webhook payload.";
|
|
115
|
+
};
|
|
116
|
+
readonly HASH_MISMATCH: {
|
|
117
|
+
readonly message: "SHA-256 hash verification failed";
|
|
118
|
+
readonly suggestion: "The raw email data may be corrupted. Try downloading from the URL instead.";
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* All error codes that can be thrown by the SDK.
|
|
123
|
+
*/
|
|
124
|
+
type WebhookErrorCode = WebhookVerificationErrorCode | WebhookPayloadErrorCode | WebhookValidationErrorCode | RawEmailDecodeErrorCode;
|
|
125
|
+
type RawEmailDecodeErrorCode = keyof typeof RAW_EMAIL_ERRORS;
|
|
126
|
+
/**
|
|
127
|
+
* Base class for all Primitive webhook errors.
|
|
128
|
+
*
|
|
129
|
+
* Catch this to handle any error from the SDK in a single catch block.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* import { handleWebhook, PrimitiveWebhookError } from '@primitivedotdev/sdk';
|
|
134
|
+
*
|
|
135
|
+
* try {
|
|
136
|
+
* const event = handleWebhook({ body, headers, secret });
|
|
137
|
+
* } catch (err) {
|
|
138
|
+
* if (err instanceof PrimitiveWebhookError) {
|
|
139
|
+
* console.error(`[${err.code}] ${err.message}`);
|
|
140
|
+
* return res.status(400).json({ error: err.code });
|
|
141
|
+
* }
|
|
142
|
+
* throw err;
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare abstract class PrimitiveWebhookError extends Error {
|
|
147
|
+
/** Programmatic error code for monitoring and handling */
|
|
148
|
+
abstract readonly code: WebhookErrorCode;
|
|
149
|
+
/** Actionable guidance for fixing the issue */
|
|
150
|
+
abstract readonly suggestion: string;
|
|
151
|
+
/**
|
|
152
|
+
* Formats the error for logging/display.
|
|
153
|
+
*/
|
|
154
|
+
toString(): string;
|
|
155
|
+
/**
|
|
156
|
+
* Serializes cleanly for structured logging (Datadog, CloudWatch, etc.)
|
|
157
|
+
*/
|
|
158
|
+
toJSON(): {
|
|
159
|
+
name: string;
|
|
160
|
+
code: WebhookErrorCode;
|
|
161
|
+
message: string;
|
|
162
|
+
suggestion: string;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Error codes for webhook verification failures.
|
|
167
|
+
* Derived from VERIFICATION_ERRORS keys.
|
|
168
|
+
*/
|
|
169
|
+
type WebhookVerificationErrorCode = keyof typeof VERIFICATION_ERRORS;
|
|
170
|
+
/**
|
|
171
|
+
* Error thrown when webhook signature verification fails.
|
|
172
|
+
*
|
|
173
|
+
* Use the `code` property to programmatically handle specific error cases.
|
|
174
|
+
*/
|
|
175
|
+
declare class WebhookVerificationError extends PrimitiveWebhookError {
|
|
176
|
+
readonly code: WebhookVerificationErrorCode;
|
|
177
|
+
readonly suggestion: string;
|
|
178
|
+
constructor(code: WebhookVerificationErrorCode, message?: string, suggestion?: string);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Error codes for webhook payload parsing failures.
|
|
182
|
+
* Derived from PAYLOAD_ERRORS keys.
|
|
183
|
+
*/
|
|
184
|
+
type WebhookPayloadErrorCode = keyof typeof PAYLOAD_ERRORS;
|
|
185
|
+
/**
|
|
186
|
+
* Error thrown when webhook payload parsing fails (lightweight parser).
|
|
187
|
+
*
|
|
188
|
+
* Use the `code` property for programmatic handling and monitoring.
|
|
189
|
+
* The `suggestion` property contains actionable guidance for fixing the issue.
|
|
190
|
+
*/
|
|
191
|
+
declare class WebhookPayloadError extends PrimitiveWebhookError {
|
|
192
|
+
readonly code: WebhookPayloadErrorCode;
|
|
193
|
+
readonly suggestion: string;
|
|
194
|
+
/** Original error if this wraps another error (e.g., JSON.parse failure) */
|
|
195
|
+
readonly cause?: Error;
|
|
196
|
+
constructor(code: WebhookPayloadErrorCode, message?: string, suggestion?: string, cause?: Error);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Error code for schema validation failures.
|
|
200
|
+
*/
|
|
201
|
+
type WebhookValidationErrorCode = "SCHEMA_VALIDATION_FAILED";
|
|
202
|
+
/**
|
|
203
|
+
* Error thrown when schema validation fails.
|
|
204
|
+
*/
|
|
205
|
+
declare class WebhookValidationError extends PrimitiveWebhookError {
|
|
206
|
+
readonly code: WebhookValidationErrorCode;
|
|
207
|
+
readonly suggestion: string;
|
|
208
|
+
/** The specific field path that failed (e.g., "email.headers.from") */
|
|
209
|
+
readonly field: string;
|
|
210
|
+
/** Original schema validation errors for advanced debugging */
|
|
211
|
+
readonly validationErrors: readonly ErrorObject[];
|
|
212
|
+
/** Number of additional validation errors beyond the first */
|
|
213
|
+
readonly additionalErrorCount: number;
|
|
214
|
+
constructor(field: string, message: string, suggestion: string, validationErrors: readonly ErrorObject[]);
|
|
215
|
+
/**
|
|
216
|
+
* Formats the error for logging/display.
|
|
217
|
+
* Includes error count and suggestion.
|
|
218
|
+
*/
|
|
219
|
+
toString(): string;
|
|
220
|
+
/**
|
|
221
|
+
* Serializes cleanly for structured logging (Datadog, CloudWatch, etc.)
|
|
222
|
+
*/
|
|
223
|
+
toJSON(): {
|
|
224
|
+
name: string;
|
|
225
|
+
code: "SCHEMA_VALIDATION_FAILED";
|
|
226
|
+
field: string;
|
|
227
|
+
message: string;
|
|
228
|
+
suggestion: string;
|
|
229
|
+
additionalErrorCount: number;
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Error thrown when raw email decoding or verification fails.
|
|
234
|
+
*
|
|
235
|
+
* Use the `code` property to determine the failure reason:
|
|
236
|
+
* - `NOT_INCLUDED`: Raw email not inline, must download from URL
|
|
237
|
+
* - `HASH_MISMATCH`: SHA-256 verification failed, content may be corrupted
|
|
238
|
+
*/
|
|
239
|
+
declare class RawEmailDecodeError extends PrimitiveWebhookError {
|
|
240
|
+
readonly code: RawEmailDecodeErrorCode;
|
|
241
|
+
readonly suggestion: string;
|
|
242
|
+
constructor(code: RawEmailDecodeErrorCode, message?: string);
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
export { buildForwardSubject as _, RawEmailDecodeErrorCode as a, normalizeReceivedEmail as b, WebhookPayloadError as c, WebhookValidationErrorCode as d, WebhookVerificationError as f, ReceivedEmailThread as g, ReceivedEmailAddress as h, RawEmailDecodeError as i, WebhookPayloadErrorCode as l, ReceivedEmail as m, PrimitiveWebhookError as n, VERIFICATION_ERRORS as o, WebhookVerificationErrorCode as p, RAW_EMAIL_ERRORS as r, WebhookErrorCode as s, PAYLOAD_ERRORS as t, WebhookValidationError as u, buildReplySubject as v, parseHeaderAddress as x, formatAddress as y };
|