@sendclipper/node 0.0.0-stage → 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.
Files changed (125) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +208 -2
  4. package/dist/cjs/core.d.ts +95 -0
  5. package/dist/cjs/core.d.ts.map +1 -0
  6. package/dist/cjs/core.js +231 -0
  7. package/dist/cjs/core.js.map +1 -0
  8. package/dist/cjs/errors.d.ts +34 -0
  9. package/dist/cjs/errors.d.ts.map +1 -0
  10. package/dist/cjs/errors.js +110 -0
  11. package/dist/cjs/errors.js.map +1 -0
  12. package/dist/cjs/index.d.ts +52 -0
  13. package/dist/cjs/index.d.ts.map +1 -0
  14. package/dist/cjs/index.js +80 -0
  15. package/dist/cjs/index.js.map +1 -0
  16. package/dist/cjs/package.json +3 -0
  17. package/dist/cjs/render.d.ts +14 -0
  18. package/dist/cjs/render.d.ts.map +1 -0
  19. package/dist/cjs/render.js +64 -0
  20. package/dist/cjs/render.js.map +1 -0
  21. package/dist/cjs/resources/audience.d.ts +51 -0
  22. package/dist/cjs/resources/audience.d.ts.map +1 -0
  23. package/dist/cjs/resources/audience.js +105 -0
  24. package/dist/cjs/resources/audience.js.map +1 -0
  25. package/dist/cjs/resources/domains.d.ts +22 -0
  26. package/dist/cjs/resources/domains.d.ts.map +1 -0
  27. package/dist/cjs/resources/domains.js +45 -0
  28. package/dist/cjs/resources/domains.js.map +1 -0
  29. package/dist/cjs/resources/emails.d.ts +34 -0
  30. package/dist/cjs/resources/emails.d.ts.map +1 -0
  31. package/dist/cjs/resources/emails.js +67 -0
  32. package/dist/cjs/resources/emails.js.map +1 -0
  33. package/dist/cjs/resources/mailboxes.d.ts +27 -0
  34. package/dist/cjs/resources/mailboxes.d.ts.map +1 -0
  35. package/dist/cjs/resources/mailboxes.js +65 -0
  36. package/dist/cjs/resources/mailboxes.js.map +1 -0
  37. package/dist/cjs/resources/sandboxes.d.ts +54 -0
  38. package/dist/cjs/resources/sandboxes.d.ts.map +1 -0
  39. package/dist/cjs/resources/sandboxes.js +107 -0
  40. package/dist/cjs/resources/sandboxes.js.map +1 -0
  41. package/dist/cjs/resources/templates.d.ts +58 -0
  42. package/dist/cjs/resources/templates.d.ts.map +1 -0
  43. package/dist/cjs/resources/templates.js +97 -0
  44. package/dist/cjs/resources/templates.js.map +1 -0
  45. package/dist/cjs/resources/webhooks.d.ts +39 -0
  46. package/dist/cjs/resources/webhooks.d.ts.map +1 -0
  47. package/dist/cjs/resources/webhooks.js +75 -0
  48. package/dist/cjs/resources/webhooks.js.map +1 -0
  49. package/dist/cjs/resources/workspace.d.ts +21 -0
  50. package/dist/cjs/resources/workspace.d.ts.map +1 -0
  51. package/dist/cjs/resources/workspace.js +38 -0
  52. package/dist/cjs/resources/workspace.js.map +1 -0
  53. package/dist/cjs/types.d.ts +662 -0
  54. package/dist/cjs/types.d.ts.map +1 -0
  55. package/dist/cjs/types.js +9 -0
  56. package/dist/cjs/types.js.map +1 -0
  57. package/dist/cjs/version.d.ts +3 -0
  58. package/dist/cjs/version.d.ts.map +1 -0
  59. package/dist/cjs/version.js +6 -0
  60. package/dist/cjs/version.js.map +1 -0
  61. package/dist/cjs/webhook-signature.d.ts +38 -0
  62. package/dist/cjs/webhook-signature.d.ts.map +1 -0
  63. package/dist/cjs/webhook-signature.js +102 -0
  64. package/dist/cjs/webhook-signature.js.map +1 -0
  65. package/dist/esm/core.d.ts +95 -0
  66. package/dist/esm/core.d.ts.map +1 -0
  67. package/dist/esm/core.js +225 -0
  68. package/dist/esm/core.js.map +1 -0
  69. package/dist/esm/errors.d.ts +34 -0
  70. package/dist/esm/errors.d.ts.map +1 -0
  71. package/dist/esm/errors.js +102 -0
  72. package/dist/esm/errors.js.map +1 -0
  73. package/dist/esm/index.d.ts +52 -0
  74. package/dist/esm/index.d.ts.map +1 -0
  75. package/dist/esm/index.js +65 -0
  76. package/dist/esm/index.js.map +1 -0
  77. package/dist/esm/render.d.ts +14 -0
  78. package/dist/esm/render.d.ts.map +1 -0
  79. package/dist/esm/render.js +28 -0
  80. package/dist/esm/render.js.map +1 -0
  81. package/dist/esm/resources/audience.d.ts +51 -0
  82. package/dist/esm/resources/audience.d.ts.map +1 -0
  83. package/dist/esm/resources/audience.js +99 -0
  84. package/dist/esm/resources/audience.js.map +1 -0
  85. package/dist/esm/resources/domains.d.ts +22 -0
  86. package/dist/esm/resources/domains.d.ts.map +1 -0
  87. package/dist/esm/resources/domains.js +41 -0
  88. package/dist/esm/resources/domains.js.map +1 -0
  89. package/dist/esm/resources/emails.d.ts +34 -0
  90. package/dist/esm/resources/emails.d.ts.map +1 -0
  91. package/dist/esm/resources/emails.js +62 -0
  92. package/dist/esm/resources/emails.js.map +1 -0
  93. package/dist/esm/resources/mailboxes.d.ts +27 -0
  94. package/dist/esm/resources/mailboxes.d.ts.map +1 -0
  95. package/dist/esm/resources/mailboxes.js +60 -0
  96. package/dist/esm/resources/mailboxes.js.map +1 -0
  97. package/dist/esm/resources/sandboxes.d.ts +54 -0
  98. package/dist/esm/resources/sandboxes.d.ts.map +1 -0
  99. package/dist/esm/resources/sandboxes.js +102 -0
  100. package/dist/esm/resources/sandboxes.js.map +1 -0
  101. package/dist/esm/resources/templates.d.ts +58 -0
  102. package/dist/esm/resources/templates.d.ts.map +1 -0
  103. package/dist/esm/resources/templates.js +91 -0
  104. package/dist/esm/resources/templates.js.map +1 -0
  105. package/dist/esm/resources/webhooks.d.ts +39 -0
  106. package/dist/esm/resources/webhooks.d.ts.map +1 -0
  107. package/dist/esm/resources/webhooks.js +70 -0
  108. package/dist/esm/resources/webhooks.js.map +1 -0
  109. package/dist/esm/resources/workspace.d.ts +21 -0
  110. package/dist/esm/resources/workspace.d.ts.map +1 -0
  111. package/dist/esm/resources/workspace.js +32 -0
  112. package/dist/esm/resources/workspace.js.map +1 -0
  113. package/dist/esm/types.d.ts +662 -0
  114. package/dist/esm/types.d.ts.map +1 -0
  115. package/dist/esm/types.js +8 -0
  116. package/dist/esm/types.js.map +1 -0
  117. package/dist/esm/version.d.ts +3 -0
  118. package/dist/esm/version.d.ts.map +1 -0
  119. package/dist/esm/version.js +3 -0
  120. package/dist/esm/version.js.map +1 -0
  121. package/dist/esm/webhook-signature.d.ts +38 -0
  122. package/dist/esm/webhook-signature.d.ts.map +1 -0
  123. package/dist/esm/webhook-signature.js +95 -0
  124. package/dist/esm/webhook-signature.js.map +1 -0
  125. package/package.json +72 -4
@@ -0,0 +1,3 @@
1
+ /** Kept equal to package.json's `version` by `test/version.test.ts`. */
2
+ export declare const VERSION = "0.1.0";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ /** Kept equal to package.json's `version` by `test/version.test.ts`. */
5
+ exports.VERSION = '0.1.0';
6
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;AAAA,wEAAwE;AAC3D,QAAA,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { WebhookEvent } from './types.js';
2
+ /** SendClipper's documented tolerance: five minutes. */
3
+ export declare const WEBHOOK_TOLERANCE_SECONDS = 300;
4
+ export declare const SIGNATURE_HEADER = "x-sendclipper-signature-v2";
5
+ export type HeaderSource = Headers | Record<string, string | string[] | undefined>
6
+ /** Just the `X-SendClipper-Signature-V2` value. */
7
+ | string;
8
+ export interface VerifyWebhookParams {
9
+ /** The body EXACTLY as received: a string or the raw bytes. Never re-serialised JSON. */
10
+ payload: string | Uint8Array;
11
+ /** The request's headers (Node, Express or Fetch style), or the signature header's value. */
12
+ headers: HeaderSource;
13
+ /** The endpoint's `whsec_…` signing secret. */
14
+ secret: string;
15
+ /** Default 300. */
16
+ toleranceSeconds?: number;
17
+ /** For tests: the current time in seconds. */
18
+ nowSeconds?: number;
19
+ }
20
+ /**
21
+ * Verify a webhook delivery and return its parsed event.
22
+ *
23
+ * SendClipper signs `${timestamp}.${rawBody}` with HMAC-SHA256 and sends
24
+ * `X-SendClipper-Signature-V2: t=<seconds>,v1=<hex>[,v1=<hex>]`. During a
25
+ * secret rotation there are two `v1=` values; any match is accepted.
26
+ *
27
+ * @throws WebhookVerificationError when any check fails.
28
+ */
29
+ export declare function verifyWebhook(params: VerifyWebhookParams): WebhookEvent;
30
+ /** `hex(HMAC-SHA256(secret, `${timestamp}.${rawBody}`))`. Exported for tests of your own handler. */
31
+ export declare function signPayload(secret: string, timestamp: number, rawBody: string): string;
32
+ /** Build a valid header for a body, e.g. to test your endpoint locally. */
33
+ export declare function signatureHeader(secret: string, rawBody: string, timestamp?: number): string;
34
+ export declare function parseSignatureHeader(header: string): {
35
+ timestamp: number | null;
36
+ signatures: string[];
37
+ };
38
+ //# sourceMappingURL=webhook-signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-signature.d.ts","sourceRoot":"","sources":["../../src/webhook-signature.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,eAAO,MAAM,gBAAgB,+BAA+B,CAAC;AAE7D,MAAM,MAAM,YAAY,GACpB,OAAO,GACP,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAC/C,mDAAmD;GACjD,MAAM,CAAC;AAEX,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6FAA6F;IAC7F,OAAO,EAAE,YAAY,CAAC;IACtB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,YAAY,CA6CvE;AAED,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,SAAgC,GAAG,MAAM,CAElH;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAWvG"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SIGNATURE_HEADER = exports.WEBHOOK_TOLERANCE_SECONDS = void 0;
4
+ exports.verifyWebhook = verifyWebhook;
5
+ exports.signPayload = signPayload;
6
+ exports.signatureHeader = signatureHeader;
7
+ exports.parseSignatureHeader = parseSignatureHeader;
8
+ const node_crypto_1 = require("node:crypto");
9
+ const errors_js_1 = require("./errors.js");
10
+ /** SendClipper's documented tolerance: five minutes. */
11
+ exports.WEBHOOK_TOLERANCE_SECONDS = 300;
12
+ exports.SIGNATURE_HEADER = 'x-sendclipper-signature-v2';
13
+ /**
14
+ * Verify a webhook delivery and return its parsed event.
15
+ *
16
+ * SendClipper signs `${timestamp}.${rawBody}` with HMAC-SHA256 and sends
17
+ * `X-SendClipper-Signature-V2: t=<seconds>,v1=<hex>[,v1=<hex>]`. During a
18
+ * secret rotation there are two `v1=` values; any match is accepted.
19
+ *
20
+ * @throws WebhookVerificationError when any check fails.
21
+ */
22
+ function verifyWebhook(params) {
23
+ const { secret } = params;
24
+ if (!secret)
25
+ throw new errors_js_1.WebhookVerificationError('missing_header', 'No webhook signing secret was given.');
26
+ if (typeof params.payload !== 'string' && !(params.payload instanceof Uint8Array)) {
27
+ throw new errors_js_1.WebhookVerificationError('invalid_payload', 'payload must be the raw request body (string or Buffer), not parsed JSON. ' +
28
+ 'With Express use express.raw({ type: "application/json" }) on the webhook route.');
29
+ }
30
+ const rawBody = typeof params.payload === 'string' ? params.payload : Buffer.from(params.payload).toString('utf8');
31
+ const header = signatureHeaderOf(params.headers);
32
+ if (!header) {
33
+ throw new errors_js_1.WebhookVerificationError('missing_header', 'The request carries no X-SendClipper-Signature-V2 header.');
34
+ }
35
+ const { timestamp, signatures } = parseSignatureHeader(header);
36
+ if (timestamp === null || signatures.length === 0) {
37
+ throw new errors_js_1.WebhookVerificationError('malformed_header', 'X-SendClipper-Signature-V2 has no t= or no v1= value.');
38
+ }
39
+ const tolerance = params.toleranceSeconds ?? exports.WEBHOOK_TOLERANCE_SECONDS;
40
+ const now = params.nowSeconds ?? Math.floor(Date.now() / 1000);
41
+ if (Math.abs(now - timestamp) > tolerance) {
42
+ throw new errors_js_1.WebhookVerificationError('timestamp_out_of_tolerance', `The delivery was signed ${Math.abs(now - timestamp)} s from now, outside the ${tolerance} s tolerance.`);
43
+ }
44
+ const expected = signPayload(secret, timestamp, rawBody);
45
+ // ANY match, and every candidate is compared: a rotation sends two.
46
+ let matched = false;
47
+ for (const candidate of signatures) {
48
+ if (constantTimeEquals(expected, candidate))
49
+ matched = true;
50
+ }
51
+ if (!matched) {
52
+ throw new errors_js_1.WebhookVerificationError('signature_mismatch', 'The signature does not match this secret and body.');
53
+ }
54
+ try {
55
+ return JSON.parse(rawBody);
56
+ }
57
+ catch {
58
+ throw new errors_js_1.WebhookVerificationError('invalid_payload', 'The signed body is not JSON.');
59
+ }
60
+ }
61
+ /** `hex(HMAC-SHA256(secret, `${timestamp}.${rawBody}`))`. Exported for tests of your own handler. */
62
+ function signPayload(secret, timestamp, rawBody) {
63
+ return (0, node_crypto_1.createHmac)('sha256', secret).update(`${timestamp}.${rawBody}`, 'utf8').digest('hex');
64
+ }
65
+ /** Build a valid header for a body, e.g. to test your endpoint locally. */
66
+ function signatureHeader(secret, rawBody, timestamp = Math.floor(Date.now() / 1000)) {
67
+ return `t=${timestamp},v1=${signPayload(secret, timestamp, rawBody)}`;
68
+ }
69
+ function parseSignatureHeader(header) {
70
+ const result = { timestamp: null, signatures: [] };
71
+ for (const part of header.split(',')) {
72
+ const eq = part.indexOf('=');
73
+ if (eq < 0)
74
+ continue;
75
+ const key = part.slice(0, eq).trim();
76
+ const value = part.slice(eq + 1).trim();
77
+ if (key === 't' && /^\d+$/.test(value))
78
+ result.timestamp = Number(value);
79
+ else if (key === 'v1' && value)
80
+ result.signatures.push(value);
81
+ }
82
+ return result;
83
+ }
84
+ function signatureHeaderOf(source) {
85
+ if (typeof source === 'string')
86
+ return source;
87
+ if (typeof source.get === 'function')
88
+ return source.get(exports.SIGNATURE_HEADER) ?? undefined;
89
+ for (const [k, v] of Object.entries(source)) {
90
+ if (k.toLowerCase() === exports.SIGNATURE_HEADER)
91
+ return Array.isArray(v) ? v.join(',') : v;
92
+ }
93
+ return undefined;
94
+ }
95
+ function constantTimeEquals(a, b) {
96
+ const left = Buffer.from(a, 'utf8');
97
+ const right = Buffer.from(b, 'utf8');
98
+ if (left.length !== right.length)
99
+ return false;
100
+ return (0, node_crypto_1.timingSafeEqual)(left, right);
101
+ }
102
+ //# sourceMappingURL=webhook-signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-signature.js","sourceRoot":"","sources":["../../src/webhook-signature.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ export declare const DEFAULT_BASE_URL = "https://sendclipper.com";
2
+ export interface ClientOptions {
3
+ /** Defaults to `process.env.SENDCLIPPER_API_KEY`. A `sc_live_…` key. */
4
+ apiKey?: string;
5
+ /** Defaults to `process.env.SENDCLIPPER_BASE_URL`, then `https://sendclipper.com`. No `/v1`. */
6
+ baseUrl?: string;
7
+ /** Per attempt, in milliseconds. Default 30 000. */
8
+ timeoutMs?: number;
9
+ /**
10
+ * Retries after a network error, a 429 or a 5xx. Default 2. A mutation is
11
+ * only retried when it carries an `Idempotency-Key`, which the SDK adds to
12
+ * every JSON mutation, so a retried send can never go out twice.
13
+ */
14
+ maxRetries?: number;
15
+ /** Your own `fetch` (a proxy agent, a test double). Defaults to the global one. */
16
+ fetch?: typeof fetch;
17
+ /** Sent on every request. */
18
+ defaultHeaders?: Record<string, string>;
19
+ }
20
+ /** What an API refusal or a network failure looks like. */
21
+ export interface ErrorResponse {
22
+ /** SendClipper's machine-readable code: `domain_not_verified`, `validation_failed`, `rate_limited`, `network_error`, … */
23
+ name: string;
24
+ /** A sentence meant for a person. */
25
+ message: string;
26
+ /** The HTTP status, or `null` when no answer arrived (network, timeout, abort). */
27
+ statusCode: number | null;
28
+ /** Extra, code-specific facts (e.g. the missing template tags). */
29
+ detail?: unknown;
30
+ }
31
+ /** Every API method resolves to this. Exactly one of `data` and `error` is set. */
32
+ export type Result<T> = ({
33
+ data: T;
34
+ error: null;
35
+ } | {
36
+ data: null;
37
+ error: ErrorResponse;
38
+ }) & {
39
+ headers: Record<string, string> | null;
40
+ };
41
+ export type Query = Record<string, string | number | boolean | undefined | null>;
42
+ export interface RequestOptions {
43
+ /** Your own key, reused when YOU retry the same logical call later. */
44
+ idempotencyKey?: string;
45
+ /** Override the client's timeout for this call. */
46
+ timeoutMs?: number;
47
+ /** Override the client's retry count for this call. */
48
+ maxRetries?: number;
49
+ /** Abort the call from outside. */
50
+ signal?: AbortSignal;
51
+ /** Extra headers for this call. */
52
+ headers?: Record<string, string>;
53
+ }
54
+ /** What a download resolves to. */
55
+ export interface DownloadedFile {
56
+ bytes: Uint8Array;
57
+ contentType: string | null;
58
+ filename: string | null;
59
+ }
60
+ interface InternalRequest extends RequestOptions {
61
+ method: string;
62
+ path: string;
63
+ query?: Query;
64
+ body?: unknown;
65
+ form?: FormData;
66
+ as?: 'json' | 'text' | 'bytes';
67
+ /** Replaces the API key (the sandbox JSON door takes an `sc_sbx_` token). */
68
+ bearer?: string;
69
+ }
70
+ export declare class HttpClient {
71
+ readonly baseUrl: string;
72
+ private readonly apiKey;
73
+ private readonly timeoutMs;
74
+ private readonly maxRetries;
75
+ private readonly fetchImpl;
76
+ private readonly defaultHeaders;
77
+ constructor(opts?: ClientOptions);
78
+ /**
79
+ * The public face of every API method: never throws for an API refusal or
80
+ * a network failure, it answers `{ data, error, headers }`.
81
+ */
82
+ call<T>(req: InternalRequest): Promise<Result<T>>;
83
+ /** Like `call()`, but throws `SendClipperApiError` / `SendClipperConnectionError`. */
84
+ request<T>(req: InternalRequest): Promise<{
85
+ data: T;
86
+ headers: Record<string, string>;
87
+ }>;
88
+ private fetchOnce;
89
+ }
90
+ /** `encodeURIComponent` for one path segment. */
91
+ export declare function seg(value: string): string;
92
+ /** Turn a `Result` back into a value, throwing its error. Used by the iterators. */
93
+ export declare function unwrap<T>(res: Result<T>): T;
94
+ export {};
95
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,225 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { SendClipperApiError, SendClipperConnectionError, toApiError } from './errors.js';
3
+ import { VERSION } from './version.js';
4
+ export const DEFAULT_BASE_URL = 'https://sendclipper.com';
5
+ const RETRYABLE_STATUS = new Set([408, 429, 500, 502, 503, 504]);
6
+ const MAX_RETRY_AFTER_MS = 60_000;
7
+ export class HttpClient {
8
+ baseUrl;
9
+ apiKey;
10
+ timeoutMs;
11
+ maxRetries;
12
+ fetchImpl;
13
+ defaultHeaders;
14
+ constructor(opts = {}) {
15
+ const env = typeof process !== 'undefined' ? process.env : {};
16
+ this.apiKey = opts.apiKey ?? env.SENDCLIPPER_API_KEY;
17
+ this.baseUrl = (opts.baseUrl ?? env.SENDCLIPPER_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, '').replace(/\/v1$/, '');
18
+ this.timeoutMs = opts.timeoutMs ?? 30_000;
19
+ this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
20
+ const f = opts.fetch ?? globalThis.fetch;
21
+ if (typeof f !== 'function') {
22
+ throw new Error('@sendclipper/node needs a global fetch (Node.js 18 or newer) or a `fetch` option.');
23
+ }
24
+ this.fetchImpl = f;
25
+ this.defaultHeaders = opts.defaultHeaders ?? {};
26
+ }
27
+ /**
28
+ * The public face of every API method: never throws for an API refusal or
29
+ * a network failure, it answers `{ data, error, headers }`.
30
+ */
31
+ async call(req) {
32
+ try {
33
+ const { data, headers } = await this.request(req);
34
+ return { data, error: null, headers };
35
+ }
36
+ catch (err) {
37
+ if (err instanceof SendClipperApiError) {
38
+ return { data: null, error: errorResponseOf(err), headers: err.headers ? headersOf(err.headers) : null };
39
+ }
40
+ if (err instanceof SendClipperConnectionError) {
41
+ return { data: null, error: { name: 'network_error', message: err.message, statusCode: null }, headers: null };
42
+ }
43
+ if (req.signal?.aborted) {
44
+ return {
45
+ data: null,
46
+ error: { name: 'network_error', message: 'The request was aborted.', statusCode: null },
47
+ headers: null,
48
+ };
49
+ }
50
+ throw err;
51
+ }
52
+ }
53
+ /** Like `call()`, but throws `SendClipperApiError` / `SendClipperConnectionError`. */
54
+ async request(req) {
55
+ const bearer = req.bearer ?? this.apiKey;
56
+ if (!bearer) {
57
+ throw new SendClipperApiError(401, 'api_key_missing', 'No SendClipper API key. Pass one to `new SendClipper("sc_live_…")` or set SENDCLIPPER_API_KEY.');
58
+ }
59
+ const url = new URL(`${this.baseUrl}/v1${req.path.startsWith('/') ? req.path : `/${req.path}`}`);
60
+ for (const [k, v] of Object.entries(req.query ?? {})) {
61
+ if (v !== undefined && v !== null && v !== '')
62
+ url.searchParams.set(k, String(v));
63
+ }
64
+ const headers = {
65
+ Authorization: `Bearer ${bearer}`,
66
+ Accept: req.as === 'json' || !req.as ? 'application/json' : '*/*',
67
+ 'User-Agent': `sendclipper-node/${VERSION}`,
68
+ ...this.defaultHeaders,
69
+ ...(req.headers ?? {}),
70
+ };
71
+ let body;
72
+ if (req.form) {
73
+ body = req.form;
74
+ }
75
+ else if (req.body !== undefined) {
76
+ headers['Content-Type'] = 'application/json';
77
+ body = JSON.stringify(req.body);
78
+ }
79
+ const isRead = req.method === 'GET' || req.method === 'HEAD';
80
+ // The API de-duplicates every JSON mutation on this header for 24 hours.
81
+ // A multipart body is not fingerprinted server-side, so it gets no key and
82
+ // is never retried.
83
+ if (!isRead && !req.form) {
84
+ headers['Idempotency-Key'] = req.idempotencyKey ?? `sc-node-${randomUUID()}`;
85
+ }
86
+ else if (req.idempotencyKey) {
87
+ headers['Idempotency-Key'] = req.idempotencyKey;
88
+ }
89
+ const canRetry = isRead || Boolean(headers['Idempotency-Key']);
90
+ const maxRetries = canRetry ? Math.max(0, req.maxRetries ?? this.maxRetries) : 0;
91
+ for (let attempt = 0;; attempt++) {
92
+ let res;
93
+ try {
94
+ res = await this.fetchOnce(url, req.method, headers, body, req.timeoutMs ?? this.timeoutMs, req.signal);
95
+ }
96
+ catch (err) {
97
+ if (req.signal?.aborted)
98
+ throw err;
99
+ if (attempt < maxRetries) {
100
+ await sleep(backoffMs(attempt));
101
+ continue;
102
+ }
103
+ throw err;
104
+ }
105
+ if (!res.ok && RETRYABLE_STATUS.has(res.status) && attempt < maxRetries) {
106
+ await res.body?.cancel().catch(() => undefined);
107
+ await sleep(retryAfterMs(res.headers) ?? backoffMs(attempt));
108
+ continue;
109
+ }
110
+ if (!res.ok) {
111
+ const err = toApiError(res.status, await res.text(), res.headers);
112
+ // Our own earlier attempt is still running server-side (a timeout
113
+ // that was not a failure). Its result is replayed once it finishes.
114
+ if (err.code === 'idempotency_key_in_flight' && attempt < maxRetries) {
115
+ await sleep(retryAfterMs(res.headers) ?? backoffMs(attempt));
116
+ continue;
117
+ }
118
+ throw err;
119
+ }
120
+ const responseHeaders = headersOf(res.headers);
121
+ if (req.as === 'bytes') {
122
+ const file = {
123
+ bytes: new Uint8Array(await res.arrayBuffer()),
124
+ contentType: res.headers.get('content-type'),
125
+ filename: filenameOf(res.headers.get('content-disposition')),
126
+ };
127
+ return { data: file, headers: responseHeaders };
128
+ }
129
+ const text = await res.text();
130
+ if (req.as === 'text' || !text)
131
+ return { data: (req.as === 'text' ? text : null), headers: responseHeaders };
132
+ try {
133
+ return { data: JSON.parse(text), headers: responseHeaders };
134
+ }
135
+ catch {
136
+ return { data: text, headers: responseHeaders };
137
+ }
138
+ }
139
+ }
140
+ async fetchOnce(url, method, headers, body, timeoutMs, outer) {
141
+ const ctrl = new AbortController();
142
+ const onAbort = () => ctrl.abort(outer?.reason);
143
+ if (outer) {
144
+ if (outer.aborted)
145
+ ctrl.abort(outer.reason);
146
+ else
147
+ outer.addEventListener('abort', onAbort, { once: true });
148
+ }
149
+ const timer = setTimeout(() => ctrl.abort(new Error('timeout')), timeoutMs);
150
+ try {
151
+ return await this.fetchImpl(url, { method, headers, body, signal: ctrl.signal });
152
+ }
153
+ catch (err) {
154
+ if (outer?.aborted)
155
+ throw err;
156
+ const timedOut = ctrl.signal.aborted;
157
+ throw new SendClipperConnectionError(timedOut
158
+ ? `SendClipper did not answer within ${timeoutMs} ms (${method} ${url.pathname}).`
159
+ : `Could not reach SendClipper at ${this.baseUrl}: ${err.message}`, err);
160
+ }
161
+ finally {
162
+ clearTimeout(timer);
163
+ outer?.removeEventListener('abort', onAbort);
164
+ }
165
+ }
166
+ }
167
+ function backoffMs(attempt) {
168
+ const base = Math.min(8_000, 500 * 2 ** attempt);
169
+ return base / 2 + Math.random() * (base / 2);
170
+ }
171
+ function retryAfterMs(headers) {
172
+ const raw = headers.get('retry-after');
173
+ if (!raw)
174
+ return undefined;
175
+ const seconds = Number(raw);
176
+ if (Number.isFinite(seconds) && seconds >= 0)
177
+ return Math.min(MAX_RETRY_AFTER_MS, seconds * 1000);
178
+ const at = Date.parse(raw);
179
+ if (Number.isNaN(at))
180
+ return undefined;
181
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, at - Date.now()));
182
+ }
183
+ function filenameOf(disposition) {
184
+ if (!disposition)
185
+ return null;
186
+ const star = /filename\*\s*=\s*(?:UTF-8'')?([^;]+)/i.exec(disposition);
187
+ if (star?.[1]) {
188
+ try {
189
+ return decodeURIComponent(star[1].trim().replace(/^"|"$/g, ''));
190
+ }
191
+ catch {
192
+ /* fall through to the plain form */
193
+ }
194
+ }
195
+ const plain = /filename\s*=\s*"?([^";]+)"?/i.exec(disposition);
196
+ return plain?.[1]?.trim() ?? null;
197
+ }
198
+ function sleep(ms) {
199
+ return new Promise((resolve) => setTimeout(resolve, ms));
200
+ }
201
+ /** `encodeURIComponent` for one path segment. */
202
+ export function seg(value) {
203
+ return encodeURIComponent(value);
204
+ }
205
+ function headersOf(h) {
206
+ const out = {};
207
+ h.forEach((value, key) => {
208
+ out[key] = value;
209
+ });
210
+ return out;
211
+ }
212
+ function errorResponseOf(err) {
213
+ const e = { name: err.code, message: err.message, statusCode: err.status };
214
+ if (err.detail !== undefined)
215
+ e.detail = err.detail;
216
+ return e;
217
+ }
218
+ /** Turn a `Result` back into a value, throwing its error. Used by the iterators. */
219
+ export function unwrap(res) {
220
+ if (res.error) {
221
+ throw new SendClipperApiError(res.error.statusCode ?? 0, res.error.name, res.error.message, res.error.detail);
222
+ }
223
+ return res.data;
224
+ }
225
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /** Base class for everything this SDK throws on purpose. */
2
+ export declare class SendClipperError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /**
6
+ * The API answered with a non-2xx status. `code` is the API's own
7
+ * machine-readable reason (`domain_not_verified`, `template_variables_missing`,
8
+ * `rate_limited`, …) and is the field to branch on; `message` is a sentence
9
+ * meant for a person.
10
+ */
11
+ export declare class SendClipperApiError extends SendClipperError {
12
+ readonly status: number;
13
+ readonly code: string;
14
+ readonly detail?: unknown | undefined;
15
+ readonly headers?: Headers | undefined;
16
+ constructor(status: number, code: string, message: string, detail?: unknown | undefined, headers?: Headers | undefined);
17
+ }
18
+ /** The request never got an answer: DNS, TLS, a reset socket or the timeout. */
19
+ export declare class SendClipperConnectionError extends SendClipperError {
20
+ readonly cause?: unknown | undefined;
21
+ constructor(message: string, cause?: unknown | undefined);
22
+ }
23
+ /** `webhooks.verify()` refused a delivery. `reason` says which check failed. */
24
+ export declare class WebhookVerificationError extends SendClipperError {
25
+ readonly reason: 'missing_header' | 'malformed_header' | 'timestamp_out_of_tolerance' | 'signature_mismatch' | 'invalid_payload';
26
+ constructor(reason: 'missing_header' | 'malformed_header' | 'timestamp_out_of_tolerance' | 'signature_mismatch' | 'invalid_payload', message: string);
27
+ }
28
+ /**
29
+ * The API answers refusals in two shapes: the house envelope
30
+ * `{ error: { code, message, detail? } }` and Nest's default
31
+ * `{ statusCode, message: string | string[], error }` (request validation).
32
+ */
33
+ export declare function toApiError(status: number, text: string, headers?: Headers): SendClipperApiError;
34
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,gBAAgB;IAErD,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;IAErB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO;gBAJjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,OAAO,YAAA,EAChB,OAAO,CAAC,EAAE,OAAO,YAAA;CAK7B;AAED,gFAAgF;AAChF,qBAAa,0BAA2B,SAAQ,gBAAgB;IAG5D,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBADxB,OAAO,EAAE,MAAM,EACN,KAAK,CAAC,EAAE,OAAO,YAAA;CAK3B;AAED,gFAAgF;AAChF,qBAAa,wBAAyB,SAAQ,gBAAgB;IAE1D,QAAQ,CAAC,MAAM,EACX,gBAAgB,GAChB,kBAAkB,GAClB,4BAA4B,GAC5B,oBAAoB,GACpB,iBAAiB;gBALZ,MAAM,EACX,gBAAgB,GAChB,kBAAkB,GAClB,4BAA4B,GAC5B,oBAAoB,GACpB,iBAAiB,EACrB,OAAO,EAAE,MAAM;CAKlB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAyC/F"}