@shware/workflow 2.0.1 → 3.0.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 +96 -2
- package/dist/apns-relay.cjs +4 -17
- package/dist/apns-relay.cjs.map +1 -1
- package/dist/apns-relay.mjs +4 -17
- package/dist/apns-relay.mjs.map +1 -1
- package/dist/aws/engine-step.cjs +104 -0
- package/dist/aws/engine-step.cjs.map +1 -0
- package/dist/aws/engine-step.d.cts +28 -0
- package/dist/aws/engine-step.d.cts.map +1 -0
- package/dist/aws/engine-step.d.mts +28 -0
- package/dist/aws/engine-step.d.mts.map +1 -0
- package/dist/aws/engine-step.mjs +102 -0
- package/dist/aws/engine-step.mjs.map +1 -0
- package/dist/aws/function-url.cjs +62 -0
- package/dist/aws/function-url.cjs.map +1 -0
- package/dist/aws/function-url.d.cts +38 -0
- package/dist/aws/function-url.d.cts.map +1 -0
- package/dist/aws/function-url.d.mts +38 -0
- package/dist/aws/function-url.d.mts.map +1 -0
- package/dist/aws/function-url.mjs +59 -0
- package/dist/aws/function-url.mjs.map +1 -0
- package/dist/aws/index.cjs +160 -0
- package/dist/aws/index.cjs.map +1 -0
- package/dist/aws/index.d.cts +62 -0
- package/dist/aws/index.d.cts.map +1 -0
- package/dist/aws/index.d.mts +62 -0
- package/dist/aws/index.d.mts.map +1 -0
- package/dist/aws/index.mjs +148 -0
- package/dist/aws/index.mjs.map +1 -0
- package/dist/aws/instances.cjs +176 -0
- package/dist/aws/instances.cjs.map +1 -0
- package/dist/aws/instances.d.cts +75 -0
- package/dist/aws/instances.d.cts.map +1 -0
- package/dist/aws/instances.d.mts +75 -0
- package/dist/aws/instances.d.mts.map +1 -0
- package/dist/aws/instances.mjs +172 -0
- package/dist/aws/instances.mjs.map +1 -0
- package/dist/aws/lambda-api.cjs +89 -0
- package/dist/aws/lambda-api.cjs.map +1 -0
- package/dist/aws/lambda-api.d.cts +43 -0
- package/dist/aws/lambda-api.d.cts.map +1 -0
- package/dist/aws/lambda-api.d.mts +43 -0
- package/dist/aws/lambda-api.d.mts.map +1 -0
- package/dist/aws/lambda-api.mjs +88 -0
- package/dist/aws/lambda-api.mjs.map +1 -0
- package/dist/client.d.cts +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/cloudflare/bindings.cjs +19 -0
- package/dist/cloudflare/bindings.cjs.map +1 -1
- package/dist/cloudflare/bindings.d.cts +37 -0
- package/dist/cloudflare/bindings.d.cts.map +1 -1
- package/dist/cloudflare/bindings.d.mts +37 -0
- package/dist/cloudflare/bindings.d.mts.map +1 -1
- package/dist/cloudflare/bindings.mjs +18 -1
- package/dist/cloudflare/bindings.mjs.map +1 -1
- package/dist/cloudflare/index.cjs +32 -28
- package/dist/cloudflare/index.d.cts +8 -6
- package/dist/cloudflare/index.d.mts +8 -6
- package/dist/cloudflare/index.mjs +12 -10
- package/dist/cloudflare/runner.cjs +27 -76
- package/dist/cloudflare/runner.cjs.map +1 -1
- package/dist/cloudflare/runner.d.cts +7 -2
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts +7 -2
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +26 -75
- package/dist/cloudflare/runner.mjs.map +1 -1
- package/dist/cloudflare/worker.cjs +22 -203
- package/dist/cloudflare/worker.cjs.map +1 -1
- package/dist/cloudflare/worker.d.cts +10 -56
- package/dist/cloudflare/worker.d.cts.map +1 -1
- package/dist/cloudflare/worker.d.mts +10 -56
- package/dist/cloudflare/worker.d.mts.map +1 -1
- package/dist/cloudflare/worker.mjs +20 -199
- package/dist/cloudflare/worker.mjs.map +1 -1
- package/dist/email-sender.cjs +62 -0
- package/dist/email-sender.cjs.map +1 -0
- package/dist/email-sender.d.cts +70 -0
- package/dist/email-sender.d.cts.map +1 -0
- package/dist/email-sender.d.mts +70 -0
- package/dist/email-sender.d.mts.map +1 -0
- package/dist/email-sender.mjs +60 -0
- package/dist/email-sender.mjs.map +1 -0
- package/dist/email.cjs +21 -0
- package/dist/email.cjs.map +1 -1
- package/dist/email.d.cts +6 -0
- package/dist/email.d.cts.map +1 -1
- package/dist/email.d.mts +6 -0
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +21 -1
- package/dist/email.mjs.map +1 -1
- package/dist/engine/index.cjs +1 -1
- package/dist/engine/index.mjs +1 -1
- package/dist/engine/interpreter.cjs +1 -1
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.mjs +1 -1
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +6 -1
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +6 -1
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/{cloudflare → host}/channels.cjs +13 -11
- package/dist/host/channels.cjs.map +1 -0
- package/dist/{cloudflare → host}/channels.d.cts +8 -3
- package/dist/host/channels.d.cts.map +1 -0
- package/dist/{cloudflare → host}/channels.d.mts +8 -3
- package/dist/host/channels.d.mts.map +1 -0
- package/dist/{cloudflare → host}/channels.mjs +9 -7
- package/dist/host/channels.mjs.map +1 -0
- package/dist/host/execute.cjs +82 -0
- package/dist/host/execute.cjs.map +1 -0
- package/dist/host/execute.d.cts +41 -0
- package/dist/host/execute.d.cts.map +1 -0
- package/dist/host/execute.d.mts +41 -0
- package/dist/host/execute.d.mts.map +1 -0
- package/dist/host/execute.mjs +81 -0
- package/dist/host/execute.mjs.map +1 -0
- package/dist/host/journey-host.cjs +214 -0
- package/dist/host/journey-host.cjs.map +1 -0
- package/dist/host/journey-host.d.cts +105 -0
- package/dist/host/journey-host.d.cts.map +1 -0
- package/dist/host/journey-host.d.mts +105 -0
- package/dist/host/journey-host.d.mts.map +1 -0
- package/dist/host/journey-host.mjs +210 -0
- package/dist/host/journey-host.mjs.map +1 -0
- package/dist/{cloudflare → host}/router.cjs +28 -9
- package/dist/host/router.cjs.map +1 -0
- package/dist/{cloudflare → host}/router.d.cts +2 -2
- package/dist/host/router.d.cts.map +1 -0
- package/dist/{cloudflare → host}/router.d.mts +2 -2
- package/dist/host/router.d.mts.map +1 -0
- package/dist/{cloudflare → host}/router.mjs +28 -9
- package/dist/host/router.mjs.map +1 -0
- package/dist/host/senders.cjs +63 -0
- package/dist/host/senders.cjs.map +1 -0
- package/dist/host/senders.d.cts +31 -0
- package/dist/host/senders.d.cts.map +1 -0
- package/dist/host/senders.d.mts +31 -0
- package/dist/host/senders.d.mts.map +1 -0
- package/dist/host/senders.mjs +59 -0
- package/dist/host/senders.mjs.map +1 -0
- package/dist/http2-client.cjs +88 -0
- package/dist/http2-client.cjs.map +1 -0
- package/dist/http2-client.d.cts +44 -0
- package/dist/http2-client.d.cts.map +1 -0
- package/dist/http2-client.d.mts +44 -0
- package/dist/http2-client.d.mts.map +1 -0
- package/dist/http2-client.mjs +86 -0
- package/dist/http2-client.mjs.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/react-email.cjs.map +1 -1
- package/dist/react-email.d.cts +1 -2
- package/dist/react-email.d.cts.map +1 -1
- package/dist/react-email.d.mts +1 -2
- package/dist/react-email.d.mts.map +1 -1
- package/dist/react-email.mjs.map +1 -1
- package/dist/store/d1.cjs +1 -0
- package/dist/store/d1.cjs.map +1 -1
- package/dist/store/d1.d.cts.map +1 -1
- package/dist/store/d1.d.mts.map +1 -1
- package/dist/store/d1.mjs +1 -0
- package/dist/store/d1.mjs.map +1 -1
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +63 -0
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +63 -0
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres.cjs +143 -2
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +4 -1
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +4 -1
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +143 -2
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/tables.cjs +26 -0
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts +1 -0
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +1 -0
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +26 -0
- package/dist/store/tables.mjs.map +1 -1
- package/dist/transports/cloudflare-email.cjs +59 -0
- package/dist/transports/cloudflare-email.cjs.map +1 -0
- package/dist/transports/cloudflare-email.d.cts +38 -0
- package/dist/transports/cloudflare-email.d.cts.map +1 -0
- package/dist/transports/cloudflare-email.d.mts +38 -0
- package/dist/transports/cloudflare-email.d.mts.map +1 -0
- package/dist/transports/cloudflare-email.mjs +57 -0
- package/dist/transports/cloudflare-email.mjs.map +1 -0
- package/dist/transports/ses.cjs +91 -0
- package/dist/transports/ses.cjs.map +1 -0
- package/dist/transports/ses.d.cts +39 -0
- package/dist/transports/ses.d.cts.map +1 -0
- package/dist/transports/ses.d.mts +39 -0
- package/dist/transports/ses.d.mts.map +1 -0
- package/dist/transports/ses.mjs +89 -0
- package/dist/transports/ses.mjs.map +1 -0
- package/dist/transports/sigv4.cjs +83 -0
- package/dist/transports/sigv4.cjs.map +1 -0
- package/dist/transports/sigv4.d.cts +27 -0
- package/dist/transports/sigv4.d.cts.map +1 -0
- package/dist/transports/sigv4.d.mts +27 -0
- package/dist/transports/sigv4.d.mts.map +1 -0
- package/dist/transports/sigv4.mjs +82 -0
- package/dist/transports/sigv4.mjs.map +1 -0
- package/package.json +53 -1
- package/dist/cloudflare/channels.cjs.map +0 -1
- package/dist/cloudflare/channels.d.cts.map +0 -1
- package/dist/cloudflare/channels.d.mts.map +0 -1
- package/dist/cloudflare/channels.mjs.map +0 -1
- package/dist/cloudflare/router.cjs.map +0 -1
- package/dist/cloudflare/router.d.cts.map +0 -1
- package/dist/cloudflare/router.d.mts.map +0 -1
- package/dist/cloudflare/router.mjs.map +0 -1
- package/dist/cloudflare/senders.cjs +0 -121
- package/dist/cloudflare/senders.cjs.map +0 -1
- package/dist/cloudflare/senders.d.cts +0 -72
- package/dist/cloudflare/senders.d.cts.map +0 -1
- package/dist/cloudflare/senders.d.mts +0 -72
- package/dist/cloudflare/senders.d.mts.map +0 -1
- package/dist/cloudflare/senders.mjs +0 -115
- package/dist/cloudflare/senders.mjs.map +0 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EmailBindingLike } from "../cloudflare/bindings.cjs";
|
|
2
|
+
import { EmailTransport } from "../email-sender.cjs";
|
|
3
|
+
//#region src/transports/cloudflare-email.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* `@shware/workflow/transports/cloudflare-email` — Cloudflare Email Service, through the
|
|
6
|
+
* Worker's `send_email` binding or, from anywhere else (a Lambda), its REST API. Both take the
|
|
7
|
+
* same finished email; only the call and the way each reports a failure differ.
|
|
8
|
+
*/
|
|
9
|
+
export type CloudflareEmailTransportOptions = {
|
|
10
|
+
/** The Worker's `send_email` binding. */
|
|
11
|
+
binding: EmailBindingLike;
|
|
12
|
+
accountId?: never;
|
|
13
|
+
apiToken?: never;
|
|
14
|
+
} | {
|
|
15
|
+
binding?: never;
|
|
16
|
+
accountId: string;
|
|
17
|
+
/** An API token allowed to send email for the account. */
|
|
18
|
+
apiToken: string;
|
|
19
|
+
/** Defaults to `https://api.cloudflare.com/client/v4`. */
|
|
20
|
+
endpoint?: string;
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
};
|
|
23
|
+
/** The REST API refused the email: `errors` are Cloudflare's `{ code, message }` pairs. */
|
|
24
|
+
export declare class CloudflareEmailError extends Error {
|
|
25
|
+
readonly status: number;
|
|
26
|
+
readonly errors: readonly {
|
|
27
|
+
code?: number;
|
|
28
|
+
message?: string;
|
|
29
|
+
}[];
|
|
30
|
+
readonly name = "CloudflareEmailError";
|
|
31
|
+
constructor(status: number, errors: readonly {
|
|
32
|
+
code?: number;
|
|
33
|
+
message?: string;
|
|
34
|
+
}[]);
|
|
35
|
+
}
|
|
36
|
+
export declare function cloudflareEmailTransport(options: CloudflareEmailTransportOptions): EmailTransport;
|
|
37
|
+
//#endregion
|
|
38
|
+
//# sourceMappingURL=cloudflare-email.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-email.d.cts","names":[],"sources":["../../src/transports/cloudflare-email.ts"],"mappings":";;;;;;;;YAUY;;EAGN,SAAS;EACT;EACA;;EAGA;EACA;;EAEA;;EAEA;EACA,eAAe;;;qBAIR,6BAA6B;WAG7B;WACA;IAAmB;IAAe;;WAH3B;EAClB,YACW,gBACA;IAAmB;IAAe;;;wBAO/B,yBAAyB,SAAS,kCAAkC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EmailBindingLike } from "../cloudflare/bindings.mjs";
|
|
2
|
+
import { EmailTransport } from "../email-sender.mjs";
|
|
3
|
+
//#region src/transports/cloudflare-email.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* `@shware/workflow/transports/cloudflare-email` — Cloudflare Email Service, through the
|
|
6
|
+
* Worker's `send_email` binding or, from anywhere else (a Lambda), its REST API. Both take the
|
|
7
|
+
* same finished email; only the call and the way each reports a failure differ.
|
|
8
|
+
*/
|
|
9
|
+
export type CloudflareEmailTransportOptions = {
|
|
10
|
+
/** The Worker's `send_email` binding. */
|
|
11
|
+
binding: EmailBindingLike;
|
|
12
|
+
accountId?: never;
|
|
13
|
+
apiToken?: never;
|
|
14
|
+
} | {
|
|
15
|
+
binding?: never;
|
|
16
|
+
accountId: string;
|
|
17
|
+
/** An API token allowed to send email for the account. */
|
|
18
|
+
apiToken: string;
|
|
19
|
+
/** Defaults to `https://api.cloudflare.com/client/v4`. */
|
|
20
|
+
endpoint?: string;
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
};
|
|
23
|
+
/** The REST API refused the email: `errors` are Cloudflare's `{ code, message }` pairs. */
|
|
24
|
+
export declare class CloudflareEmailError extends Error {
|
|
25
|
+
readonly status: number;
|
|
26
|
+
readonly errors: readonly {
|
|
27
|
+
code?: number;
|
|
28
|
+
message?: string;
|
|
29
|
+
}[];
|
|
30
|
+
readonly name = "CloudflareEmailError";
|
|
31
|
+
constructor(status: number, errors: readonly {
|
|
32
|
+
code?: number;
|
|
33
|
+
message?: string;
|
|
34
|
+
}[]);
|
|
35
|
+
}
|
|
36
|
+
export declare function cloudflareEmailTransport(options: CloudflareEmailTransportOptions): EmailTransport;
|
|
37
|
+
//#endregion
|
|
38
|
+
//# sourceMappingURL=cloudflare-email.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-email.d.mts","names":[],"sources":["../../src/transports/cloudflare-email.ts"],"mappings":";;;;;;;;YAUY;;EAGN,SAAS;EACT;EACA;;EAGA;EACA;;EAEA;;EAEA;EACA,eAAe;;;qBAIR,6BAA6B;WAG7B;WACA;IAAmB;IAAe;;WAH3B;EAClB,YACW,gBACA;IAAmB;IAAe;;;wBAO/B,yBAAyB,SAAS,kCAAkC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { mimeAddress } from "../email.mjs";
|
|
2
|
+
//#region src/transports/cloudflare-email.ts
|
|
3
|
+
/** The REST API refused the email: `errors` are Cloudflare's `{ code, message }` pairs. */
|
|
4
|
+
var CloudflareEmailError = class extends Error {
|
|
5
|
+
status;
|
|
6
|
+
errors;
|
|
7
|
+
name = "CloudflareEmailError";
|
|
8
|
+
constructor(status, errors) {
|
|
9
|
+
const said = errors.map((e) => `${e.code ?? "?"} ${e.message ?? ""}`.trim()).join("; ");
|
|
10
|
+
super(`Cloudflare Email send failed (${status})${said === "" ? "" : `: ${said}`}`);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.errors = errors;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function cloudflareEmailTransport(options) {
|
|
16
|
+
if (options.binding !== void 0) {
|
|
17
|
+
const { binding } = options;
|
|
18
|
+
return { async send(message) {
|
|
19
|
+
const id = (await binding.send(message))?.messageId;
|
|
20
|
+
return typeof id === "string" ? { providerMessageId: id } : {};
|
|
21
|
+
} };
|
|
22
|
+
}
|
|
23
|
+
const given = options;
|
|
24
|
+
if (typeof given.accountId !== "string" || typeof given.apiToken !== "string") throw new TypeError("cloudflareEmailTransport needs { binding } or { accountId, apiToken }");
|
|
25
|
+
const { accountId, apiToken } = options;
|
|
26
|
+
const endpoint = (options.endpoint ?? "https://api.cloudflare.com/client/v4").replace(/\/$/, "");
|
|
27
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
28
|
+
return { async send(message) {
|
|
29
|
+
const response = await fetchImpl(`${endpoint}/accounts/${encodeURIComponent(accountId)}/email/sending/send`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
authorization: `Bearer ${apiToken}`,
|
|
33
|
+
"content-type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
from: mimeAddress(message.from),
|
|
37
|
+
to: message.to,
|
|
38
|
+
...message.replyTo === void 0 ? {} : { reply_to: mimeAddress(message.replyTo) },
|
|
39
|
+
subject: message.subject,
|
|
40
|
+
html: message.html
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
let body = {};
|
|
44
|
+
try {
|
|
45
|
+
body = await response.json();
|
|
46
|
+
} catch {
|
|
47
|
+
body = {};
|
|
48
|
+
}
|
|
49
|
+
if (!response.ok || body.success !== true) throw new CloudflareEmailError(response.status, Array.isArray(body.errors) ? body.errors : []);
|
|
50
|
+
const id = body.result?.message_id;
|
|
51
|
+
return typeof id === "string" ? { providerMessageId: id } : {};
|
|
52
|
+
} };
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { CloudflareEmailError, cloudflareEmailTransport };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=cloudflare-email.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-email.mjs","names":[],"sources":["../../src/transports/cloudflare-email.ts"],"sourcesContent":["import type { EmailBindingLike } from '../cloudflare/bindings';\nimport { mimeAddress } from '../email';\nimport type { EmailMessage, EmailTransport } from '../email-sender';\n\n/**\n * `@shware/workflow/transports/cloudflare-email` — Cloudflare Email Service, through the\n * Worker's `send_email` binding or, from anywhere else (a Lambda), its REST API. Both take the\n * same finished email; only the call and the way each reports a failure differ.\n */\n\nexport type CloudflareEmailTransportOptions =\n | {\n /** The Worker's `send_email` binding. */\n binding: EmailBindingLike;\n accountId?: never;\n apiToken?: never;\n }\n | {\n binding?: never;\n accountId: string;\n /** An API token allowed to send email for the account. */\n apiToken: string;\n /** Defaults to `https://api.cloudflare.com/client/v4`. */\n endpoint?: string;\n fetch?: typeof fetch;\n };\n\n/** The REST API refused the email: `errors` are Cloudflare's `{ code, message }` pairs. */\nexport class CloudflareEmailError extends Error {\n override readonly name = 'CloudflareEmailError';\n constructor(\n readonly status: number,\n readonly errors: readonly { code?: number; message?: string }[]\n ) {\n const said = errors.map((e) => `${e.code ?? '?'} ${e.message ?? ''}`.trim()).join('; ');\n super(`Cloudflare Email send failed (${status})${said === '' ? '' : `: ${said}`}`);\n }\n}\n\nexport function cloudflareEmailTransport(options: CloudflareEmailTransportOptions): EmailTransport {\n if (options.binding !== undefined) {\n const { binding } = options;\n return {\n async send(message) {\n // The binding takes the key and drops the duplicate a retry or replay produces\n const result: unknown = await binding.send(message);\n const id = (result as { messageId?: unknown } | null | undefined)?.messageId;\n return typeof id === 'string' ? { providerMessageId: id } : {};\n },\n };\n }\n // Checked at runtime too: a JS caller can pass neither\n const given = options as { accountId?: unknown; apiToken?: unknown };\n if (typeof given.accountId !== 'string' || typeof given.apiToken !== 'string') {\n throw new TypeError('cloudflareEmailTransport needs { binding } or { accountId, apiToken }');\n }\n const { accountId, apiToken } = options;\n const endpoint = (options.endpoint ?? 'https://api.cloudflare.com/client/v4').replace(/\\/$/, '');\n const fetchImpl = options.fetch ?? fetch;\n return {\n async send(message: EmailMessage) {\n const response = await fetchImpl(\n `${endpoint}/accounts/${encodeURIComponent(accountId)}/email/sending/send`,\n {\n method: 'POST',\n headers: {\n authorization: `Bearer ${apiToken}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n from: mimeAddress(message.from),\n to: message.to,\n ...(message.replyTo === undefined ? {} : { reply_to: mimeAddress(message.replyTo) }),\n subject: message.subject,\n html: message.html,\n }),\n }\n );\n let body: { success?: unknown; errors?: unknown; result?: { message_id?: unknown } } = {};\n try {\n body = (await response.json()) as typeof body;\n } catch {\n body = {};\n }\n if (!response.ok || body.success !== true) {\n throw new CloudflareEmailError(\n response.status,\n Array.isArray(body.errors) ? (body.errors as { code?: number; message?: string }[]) : []\n );\n }\n // No server-side de-duplication is documented for the REST API: a retry after an accepted\n // send can deliver twice (at least once, like SES)\n const id = body.result?.message_id;\n return typeof id === 'string' ? { providerMessageId: id } : {};\n },\n };\n}\n"],"mappings":";;;AA4BA,IAAa,uBAAb,cAA0C,MAAM;CAGnC;CACA;CAHX,OAAyB;CACzB,YACE,QACA,QACA;EACA,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG,EAAE,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;EACtF,MAAM,iCAAiC,OAAO,GAAG,SAAS,KAAK,KAAK,KAAK,QAAQ;EAJxE,KAAA,SAAA;EACA,KAAA,SAAA;CAIX;AACF;AAEA,SAAgB,yBAAyB,SAA0D;CACjG,IAAI,QAAQ,YAAY,KAAA,GAAW;EACjC,MAAM,EAAE,YAAY;EACpB,OAAO,EACL,MAAM,KAAK,SAAS;GAGlB,MAAM,MAAM,MADkB,QAAQ,KAAK,OAAO,EAAA,EACiB;GACnE,OAAO,OAAO,OAAO,WAAW,EAAE,mBAAmB,GAAG,IAAI,CAAC;EAC/D,EACF;CACF;CAEA,MAAM,QAAQ;CACd,IAAI,OAAO,MAAM,cAAc,YAAY,OAAO,MAAM,aAAa,UACnE,MAAM,IAAI,UAAU,uEAAuE;CAE7F,MAAM,EAAE,WAAW,aAAa;CAChC,MAAM,YAAY,QAAQ,YAAY,uCAAA,CAAwC,QAAQ,OAAO,EAAE;CAC/F,MAAM,YAAY,QAAQ,SAAS;CACnC,OAAO,EACL,MAAM,KAAK,SAAuB;EAChC,MAAM,WAAW,MAAM,UACrB,GAAG,SAAS,YAAY,mBAAmB,SAAS,EAAE,sBACtD;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU;IACnB,MAAM,YAAY,QAAQ,IAAI;IAC9B,IAAI,QAAQ;IACZ,GAAI,QAAQ,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,YAAY,QAAQ,OAAO,EAAE;IAClF,SAAS,QAAQ;IACjB,MAAM,QAAQ;GAChB,CAAC;EACH,CACF;EACA,IAAI,OAAmF,CAAC;EACxF,IAAI;GACF,OAAQ,MAAM,SAAS,KAAK;EAC9B,QAAQ;GACN,OAAO,CAAC;EACV;EACA,IAAI,CAAC,SAAS,MAAM,KAAK,YAAY,MACnC,MAAM,IAAI,qBACR,SAAS,QACT,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAmD,CAAC,CACzF;EAIF,MAAM,KAAK,KAAK,QAAQ;EACxB,OAAO,OAAO,OAAO,WAAW,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC/D,EACF;AACF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_email = require("../email.cjs");
|
|
3
|
+
const require_transports_sigv4 = require("./sigv4.cjs");
|
|
4
|
+
//#region src/transports/ses.ts
|
|
5
|
+
/** An SES error response: `type` is SES's error code (`MessageRejected`, `TooManyRequestsException`, ...). */
|
|
6
|
+
var SesSendError = class extends Error {
|
|
7
|
+
status;
|
|
8
|
+
type;
|
|
9
|
+
name = "SesSendError";
|
|
10
|
+
constructor(status, type, message) {
|
|
11
|
+
super(`SES SendEmail failed (${status}${type === void 0 ? "" : ` ${type}`}): ${message}`);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.type = type;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function environmentCredentials() {
|
|
17
|
+
const env = globalThis.process?.env;
|
|
18
|
+
const accessKeyId = env?.AWS_ACCESS_KEY_ID;
|
|
19
|
+
const secretAccessKey = env?.AWS_SECRET_ACCESS_KEY;
|
|
20
|
+
if (accessKeyId === void 0 || secretAccessKey === void 0) throw new Error("sesEmailTransport: no credentials — pass `credentials`, or run where AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set (a Lambda execution role)");
|
|
21
|
+
return {
|
|
22
|
+
accessKeyId,
|
|
23
|
+
secretAccessKey,
|
|
24
|
+
sessionToken: env?.AWS_SESSION_TOKEN
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function sesEmailTransport(options) {
|
|
28
|
+
const endpoint = (options.endpoint ?? `https://email.${options.region}.amazonaws.com`).replace(/\/$/, "");
|
|
29
|
+
const credentials = async () => {
|
|
30
|
+
const configured = options.credentials;
|
|
31
|
+
if (configured === void 0) return environmentCredentials();
|
|
32
|
+
return typeof configured === "function" ? configured() : configured;
|
|
33
|
+
};
|
|
34
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
35
|
+
return { async send(message) {
|
|
36
|
+
const body = JSON.stringify({
|
|
37
|
+
FromEmailAddress: require_email.mimeAddress(message.from),
|
|
38
|
+
Destination: { ToAddresses: [message.to] },
|
|
39
|
+
...message.replyTo === void 0 ? {} : { ReplyToAddresses: [require_email.mimeAddress(message.replyTo)] },
|
|
40
|
+
Content: { Simple: {
|
|
41
|
+
Subject: {
|
|
42
|
+
Data: message.subject,
|
|
43
|
+
Charset: "UTF-8"
|
|
44
|
+
},
|
|
45
|
+
Body: { Html: {
|
|
46
|
+
Data: message.html,
|
|
47
|
+
Charset: "UTF-8"
|
|
48
|
+
} }
|
|
49
|
+
} },
|
|
50
|
+
...options.configurationSetName === void 0 ? {} : { ConfigurationSetName: options.configurationSetName }
|
|
51
|
+
});
|
|
52
|
+
const url = `${endpoint}/v2/email/outbound-emails`;
|
|
53
|
+
const headers = await require_transports_sigv4.signRequest({
|
|
54
|
+
method: "POST",
|
|
55
|
+
url,
|
|
56
|
+
headers: { "content-type": "application/json" },
|
|
57
|
+
body,
|
|
58
|
+
credentials: await credentials(),
|
|
59
|
+
region: options.region,
|
|
60
|
+
service: "ses"
|
|
61
|
+
});
|
|
62
|
+
const response = await fetchImpl(url, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers,
|
|
65
|
+
body
|
|
66
|
+
});
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
let detail = text;
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(text);
|
|
72
|
+
const said = parsed.message ?? parsed.Message;
|
|
73
|
+
if (typeof said === "string") detail = said;
|
|
74
|
+
} catch {}
|
|
75
|
+
const type = response.headers.get("x-amzn-errortype")?.split(":")[0];
|
|
76
|
+
throw new SesSendError(response.status, type, detail);
|
|
77
|
+
}
|
|
78
|
+
let messageId;
|
|
79
|
+
try {
|
|
80
|
+
messageId = JSON.parse(text).MessageId;
|
|
81
|
+
} catch {
|
|
82
|
+
messageId = void 0;
|
|
83
|
+
}
|
|
84
|
+
return typeof messageId === "string" ? { providerMessageId: messageId } : {};
|
|
85
|
+
} };
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
exports.SesSendError = SesSendError;
|
|
89
|
+
exports.sesEmailTransport = sesEmailTransport;
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=ses.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses.cjs","names":["mimeAddress","signRequest"],"sources":["../../src/transports/ses.ts"],"sourcesContent":["import { mimeAddress } from '../email';\nimport type { EmailMessage, EmailTransport } from '../email-sender';\nimport { signRequest } from './sigv4';\n\n/**\n * `@shware/workflow/transports/ses` — email through Amazon SES (the v2 `SendEmail` API) over\n * plain `fetch` with a SigV4 signature, so the same transport runs in a Lambda and in a Worker.\n * Nothing here loads the AWS SDK or a Node-only credential chain.\n */\n\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n /** Present for temporary credentials (a role's): signed with the request. */\n sessionToken?: string | undefined;\n}\n\n/** Called on every send, so rotated temporary credentials are picked up rather than frozen. */\nexport type AwsCredentialProvider = () => AwsCredentials | Promise<AwsCredentials>;\n\nexport interface SesEmailTransportOptions {\n region: string;\n /**\n * Fixed credentials, or a provider read on every send. Defaults to the process environment's\n * `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, read at send time —\n * a Lambda's execution role. A Worker passes them explicitly from its validated env.\n */\n credentials?: AwsCredentials | AwsCredentialProvider;\n /** An SES configuration set (event publishing, a dedicated IP pool). */\n configurationSetName?: string;\n /** Defaults to `https://email.<region>.amazonaws.com`. */\n endpoint?: string;\n fetch?: typeof fetch;\n}\n\n/** An SES error response: `type` is SES's error code (`MessageRejected`, `TooManyRequestsException`, ...). */\nexport class SesSendError extends Error {\n override readonly name = 'SesSendError';\n constructor(\n readonly status: number,\n readonly type: string | undefined,\n message: string\n ) {\n super(`SES SendEmail failed (${status}${type === undefined ? '' : ` ${type}`}): ${message}`);\n }\n}\n\nfunction environmentCredentials(): AwsCredentials {\n // Read through globalThis: a Worker has no `process`, and must pass credentials instead\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n ?.env;\n const accessKeyId = env?.AWS_ACCESS_KEY_ID;\n const secretAccessKey = env?.AWS_SECRET_ACCESS_KEY;\n if (accessKeyId === undefined || secretAccessKey === undefined) {\n throw new Error(\n 'sesEmailTransport: no credentials — pass `credentials`, or run where AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set (a Lambda execution role)'\n );\n }\n return { accessKeyId, secretAccessKey, sessionToken: env?.AWS_SESSION_TOKEN };\n}\n\nexport function sesEmailTransport(options: SesEmailTransportOptions): EmailTransport {\n const endpoint = (options.endpoint ?? `https://email.${options.region}.amazonaws.com`).replace(\n /\\/$/,\n ''\n );\n const credentials = async (): Promise<AwsCredentials> => {\n const configured = options.credentials;\n if (configured === undefined) return environmentCredentials();\n return typeof configured === 'function' ? configured() : configured;\n };\n const fetchImpl = options.fetch ?? fetch;\n\n return {\n async send(message: EmailMessage) {\n const body = JSON.stringify({\n FromEmailAddress: mimeAddress(message.from),\n Destination: { ToAddresses: [message.to] },\n ...(message.replyTo === undefined\n ? {}\n : { ReplyToAddresses: [mimeAddress(message.replyTo)] }),\n Content: {\n Simple: {\n Subject: { Data: message.subject, Charset: 'UTF-8' },\n Body: { Html: { Data: message.html, Charset: 'UTF-8' } },\n },\n },\n ...(options.configurationSetName === undefined\n ? {}\n : { ConfigurationSetName: options.configurationSetName }),\n });\n const url = `${endpoint}/v2/email/outbound-emails`;\n const headers = await signRequest({\n method: 'POST',\n url,\n headers: { 'content-type': 'application/json' },\n body,\n credentials: await credentials(),\n region: options.region,\n service: 'ses',\n });\n // No retries here: the durable step owns them, and counts them — SES cannot de-duplicate,\n // so every extra attempt of an accepted send is another chance to deliver twice\n const response = await fetchImpl(url, { method: 'POST', headers, body });\n const text = await response.text();\n if (!response.ok) {\n let detail = text;\n try {\n const parsed = JSON.parse(text) as { message?: unknown; Message?: unknown };\n const said = parsed.message ?? parsed.Message;\n if (typeof said === 'string') detail = said;\n } catch {\n // Not JSON: the raw body says what went wrong\n }\n const type = response.headers.get('x-amzn-errortype')?.split(':')[0];\n throw new SesSendError(response.status, type, detail);\n }\n let messageId: unknown;\n try {\n messageId = (JSON.parse(text) as { MessageId?: unknown }).MessageId;\n } catch {\n messageId = undefined;\n }\n return typeof messageId === 'string' ? { providerMessageId: messageId } : {};\n },\n };\n}\n"],"mappings":";;;;;AAoCA,IAAa,eAAb,cAAkC,MAAM;CAG3B;CACA;CAHX,OAAyB;CACzB,YACE,QACA,MACA,SACA;EACA,MAAM,yBAAyB,SAAS,SAAS,KAAA,IAAY,KAAK,IAAI,OAAO,KAAK,SAAS;EAJlF,KAAA,SAAA;EACA,KAAA,OAAA;CAIX;AACF;AAEA,SAAS,yBAAyC;CAEhD,MAAM,MAAO,WAA0E,SACnF;CACJ,MAAM,cAAc,KAAK;CACzB,MAAM,kBAAkB,KAAK;CAC7B,IAAI,gBAAgB,KAAA,KAAa,oBAAoB,KAAA,GACnD,MAAM,IAAI,MACR,oJACF;CAEF,OAAO;EAAE;EAAa;EAAiB,cAAc,KAAK;CAAkB;AAC9E;AAEA,SAAgB,kBAAkB,SAAmD;CACnF,MAAM,YAAY,QAAQ,YAAY,iBAAiB,QAAQ,OAAO,gBAAA,CAAiB,QACrF,OACA,EACF;CACA,MAAM,cAAc,YAAqC;EACvD,MAAM,aAAa,QAAQ;EAC3B,IAAI,eAAe,KAAA,GAAW,OAAO,uBAAuB;EAC5D,OAAO,OAAO,eAAe,aAAa,WAAW,IAAI;CAC3D;CACA,MAAM,YAAY,QAAQ,SAAS;CAEnC,OAAO,EACL,MAAM,KAAK,SAAuB;EAChC,MAAM,OAAO,KAAK,UAAU;GAC1B,kBAAkBA,cAAAA,YAAY,QAAQ,IAAI;GAC1C,aAAa,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;GACzC,GAAI,QAAQ,YAAY,KAAA,IACpB,CAAC,IACD,EAAE,kBAAkB,CAACA,cAAAA,YAAY,QAAQ,OAAO,CAAC,EAAE;GACvD,SAAS,EACP,QAAQ;IACN,SAAS;KAAE,MAAM,QAAQ;KAAS,SAAS;IAAQ;IACnD,MAAM,EAAE,MAAM;KAAE,MAAM,QAAQ;KAAM,SAAS;IAAQ,EAAE;GACzD,EACF;GACA,GAAI,QAAQ,yBAAyB,KAAA,IACjC,CAAC,IACD,EAAE,sBAAsB,QAAQ,qBAAqB;EAC3D,CAAC;EACD,MAAM,MAAM,GAAG,SAAS;EACxB,MAAM,UAAU,MAAMC,yBAAAA,YAAY;GAChC,QAAQ;GACR;GACA,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C;GACA,aAAa,MAAM,YAAY;GAC/B,QAAQ,QAAQ;GAChB,SAAS;EACX,CAAC;EAGD,MAAM,WAAW,MAAM,UAAU,KAAK;GAAE,QAAQ;GAAQ;GAAS;EAAK,CAAC;EACvE,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI,CAAC,SAAS,IAAI;GAChB,IAAI,SAAS;GACb,IAAI;IACF,MAAM,SAAS,KAAK,MAAM,IAAI;IAC9B,MAAM,OAAO,OAAO,WAAW,OAAO;IACtC,IAAI,OAAO,SAAS,UAAU,SAAS;GACzC,QAAQ,CAER;GACA,MAAM,OAAO,SAAS,QAAQ,IAAI,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;GAClE,MAAM,IAAI,aAAa,SAAS,QAAQ,MAAM,MAAM;EACtD;EACA,IAAI;EACJ,IAAI;GACF,YAAa,KAAK,MAAM,IAAI,CAAC,CAA6B;EAC5D,QAAQ;GACN,YAAY,KAAA;EACd;EACA,OAAO,OAAO,cAAc,WAAW,EAAE,mBAAmB,UAAU,IAAI,CAAC;CAC7E,EACF;AACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EmailTransport } from "../email-sender.cjs";
|
|
2
|
+
//#region src/transports/ses.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* `@shware/workflow/transports/ses` — email through Amazon SES (the v2 `SendEmail` API) over
|
|
5
|
+
* plain `fetch` with a SigV4 signature, so the same transport runs in a Lambda and in a Worker.
|
|
6
|
+
* Nothing here loads the AWS SDK or a Node-only credential chain.
|
|
7
|
+
*/
|
|
8
|
+
export interface AwsCredentials {
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
secretAccessKey: string;
|
|
11
|
+
/** Present for temporary credentials (a role's): signed with the request. */
|
|
12
|
+
sessionToken?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Called on every send, so rotated temporary credentials are picked up rather than frozen. */
|
|
15
|
+
export type AwsCredentialProvider = () => AwsCredentials | Promise<AwsCredentials>;
|
|
16
|
+
export interface SesEmailTransportOptions {
|
|
17
|
+
region: string;
|
|
18
|
+
/**
|
|
19
|
+
* Fixed credentials, or a provider read on every send. Defaults to the process environment's
|
|
20
|
+
* `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, read at send time —
|
|
21
|
+
* a Lambda's execution role. A Worker passes them explicitly from its validated env.
|
|
22
|
+
*/
|
|
23
|
+
credentials?: AwsCredentials | AwsCredentialProvider;
|
|
24
|
+
/** An SES configuration set (event publishing, a dedicated IP pool). */
|
|
25
|
+
configurationSetName?: string;
|
|
26
|
+
/** Defaults to `https://email.<region>.amazonaws.com`. */
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
fetch?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
/** An SES error response: `type` is SES's error code (`MessageRejected`, `TooManyRequestsException`, ...). */
|
|
31
|
+
export declare class SesSendError extends Error {
|
|
32
|
+
readonly status: number;
|
|
33
|
+
readonly type: string | undefined;
|
|
34
|
+
readonly name = "SesSendError";
|
|
35
|
+
constructor(status: number, type: string | undefined, message: string);
|
|
36
|
+
}
|
|
37
|
+
export declare function sesEmailTransport(options: SesEmailTransportOptions): EmailTransport;
|
|
38
|
+
//#endregion
|
|
39
|
+
//# sourceMappingURL=ses.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses.d.cts","names":[],"sources":["../../src/transports/ses.ts"],"mappings":";;;;;;;iBAUiB;EACf;EACA;;EAEA;;;YAIU,8BAA8B,iBAAiB,QAAQ;iBAElD;EACf;;;;;;EAMA,cAAc,iBAAiB;;EAE/B;;EAEA;EACA,eAAe;;;qBAIJ,qBAAqB;WAGrB;WACA;WAHO;EAClB,YACW,gBACA,0BACT;;wBAoBY,kBAAkB,SAAS,2BAA2B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EmailTransport } from "../email-sender.mjs";
|
|
2
|
+
//#region src/transports/ses.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* `@shware/workflow/transports/ses` — email through Amazon SES (the v2 `SendEmail` API) over
|
|
5
|
+
* plain `fetch` with a SigV4 signature, so the same transport runs in a Lambda and in a Worker.
|
|
6
|
+
* Nothing here loads the AWS SDK or a Node-only credential chain.
|
|
7
|
+
*/
|
|
8
|
+
export interface AwsCredentials {
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
secretAccessKey: string;
|
|
11
|
+
/** Present for temporary credentials (a role's): signed with the request. */
|
|
12
|
+
sessionToken?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Called on every send, so rotated temporary credentials are picked up rather than frozen. */
|
|
15
|
+
export type AwsCredentialProvider = () => AwsCredentials | Promise<AwsCredentials>;
|
|
16
|
+
export interface SesEmailTransportOptions {
|
|
17
|
+
region: string;
|
|
18
|
+
/**
|
|
19
|
+
* Fixed credentials, or a provider read on every send. Defaults to the process environment's
|
|
20
|
+
* `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, read at send time —
|
|
21
|
+
* a Lambda's execution role. A Worker passes them explicitly from its validated env.
|
|
22
|
+
*/
|
|
23
|
+
credentials?: AwsCredentials | AwsCredentialProvider;
|
|
24
|
+
/** An SES configuration set (event publishing, a dedicated IP pool). */
|
|
25
|
+
configurationSetName?: string;
|
|
26
|
+
/** Defaults to `https://email.<region>.amazonaws.com`. */
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
fetch?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
/** An SES error response: `type` is SES's error code (`MessageRejected`, `TooManyRequestsException`, ...). */
|
|
31
|
+
export declare class SesSendError extends Error {
|
|
32
|
+
readonly status: number;
|
|
33
|
+
readonly type: string | undefined;
|
|
34
|
+
readonly name = "SesSendError";
|
|
35
|
+
constructor(status: number, type: string | undefined, message: string);
|
|
36
|
+
}
|
|
37
|
+
export declare function sesEmailTransport(options: SesEmailTransportOptions): EmailTransport;
|
|
38
|
+
//#endregion
|
|
39
|
+
//# sourceMappingURL=ses.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses.d.mts","names":[],"sources":["../../src/transports/ses.ts"],"mappings":";;;;;;;iBAUiB;EACf;EACA;;EAEA;;;YAIU,8BAA8B,iBAAiB,QAAQ;iBAElD;EACf;;;;;;EAMA,cAAc,iBAAiB;;EAE/B;;EAEA;EACA,eAAe;;;qBAIJ,qBAAqB;WAGrB;WACA;WAHO;EAClB,YACW,gBACA,0BACT;;wBAoBY,kBAAkB,SAAS,2BAA2B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mimeAddress } from "../email.mjs";
|
|
2
|
+
import { signRequest } from "./sigv4.mjs";
|
|
3
|
+
//#region src/transports/ses.ts
|
|
4
|
+
/** An SES error response: `type` is SES's error code (`MessageRejected`, `TooManyRequestsException`, ...). */
|
|
5
|
+
var SesSendError = class extends Error {
|
|
6
|
+
status;
|
|
7
|
+
type;
|
|
8
|
+
name = "SesSendError";
|
|
9
|
+
constructor(status, type, message) {
|
|
10
|
+
super(`SES SendEmail failed (${status}${type === void 0 ? "" : ` ${type}`}): ${message}`);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.type = type;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function environmentCredentials() {
|
|
16
|
+
const env = globalThis.process?.env;
|
|
17
|
+
const accessKeyId = env?.AWS_ACCESS_KEY_ID;
|
|
18
|
+
const secretAccessKey = env?.AWS_SECRET_ACCESS_KEY;
|
|
19
|
+
if (accessKeyId === void 0 || secretAccessKey === void 0) throw new Error("sesEmailTransport: no credentials — pass `credentials`, or run where AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set (a Lambda execution role)");
|
|
20
|
+
return {
|
|
21
|
+
accessKeyId,
|
|
22
|
+
secretAccessKey,
|
|
23
|
+
sessionToken: env?.AWS_SESSION_TOKEN
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function sesEmailTransport(options) {
|
|
27
|
+
const endpoint = (options.endpoint ?? `https://email.${options.region}.amazonaws.com`).replace(/\/$/, "");
|
|
28
|
+
const credentials = async () => {
|
|
29
|
+
const configured = options.credentials;
|
|
30
|
+
if (configured === void 0) return environmentCredentials();
|
|
31
|
+
return typeof configured === "function" ? configured() : configured;
|
|
32
|
+
};
|
|
33
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
34
|
+
return { async send(message) {
|
|
35
|
+
const body = JSON.stringify({
|
|
36
|
+
FromEmailAddress: mimeAddress(message.from),
|
|
37
|
+
Destination: { ToAddresses: [message.to] },
|
|
38
|
+
...message.replyTo === void 0 ? {} : { ReplyToAddresses: [mimeAddress(message.replyTo)] },
|
|
39
|
+
Content: { Simple: {
|
|
40
|
+
Subject: {
|
|
41
|
+
Data: message.subject,
|
|
42
|
+
Charset: "UTF-8"
|
|
43
|
+
},
|
|
44
|
+
Body: { Html: {
|
|
45
|
+
Data: message.html,
|
|
46
|
+
Charset: "UTF-8"
|
|
47
|
+
} }
|
|
48
|
+
} },
|
|
49
|
+
...options.configurationSetName === void 0 ? {} : { ConfigurationSetName: options.configurationSetName }
|
|
50
|
+
});
|
|
51
|
+
const url = `${endpoint}/v2/email/outbound-emails`;
|
|
52
|
+
const headers = await signRequest({
|
|
53
|
+
method: "POST",
|
|
54
|
+
url,
|
|
55
|
+
headers: { "content-type": "application/json" },
|
|
56
|
+
body,
|
|
57
|
+
credentials: await credentials(),
|
|
58
|
+
region: options.region,
|
|
59
|
+
service: "ses"
|
|
60
|
+
});
|
|
61
|
+
const response = await fetchImpl(url, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers,
|
|
64
|
+
body
|
|
65
|
+
});
|
|
66
|
+
const text = await response.text();
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
let detail = text;
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(text);
|
|
71
|
+
const said = parsed.message ?? parsed.Message;
|
|
72
|
+
if (typeof said === "string") detail = said;
|
|
73
|
+
} catch {}
|
|
74
|
+
const type = response.headers.get("x-amzn-errortype")?.split(":")[0];
|
|
75
|
+
throw new SesSendError(response.status, type, detail);
|
|
76
|
+
}
|
|
77
|
+
let messageId;
|
|
78
|
+
try {
|
|
79
|
+
messageId = JSON.parse(text).MessageId;
|
|
80
|
+
} catch {
|
|
81
|
+
messageId = void 0;
|
|
82
|
+
}
|
|
83
|
+
return typeof messageId === "string" ? { providerMessageId: messageId } : {};
|
|
84
|
+
} };
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { SesSendError, sesEmailTransport };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=ses.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses.mjs","names":[],"sources":["../../src/transports/ses.ts"],"sourcesContent":["import { mimeAddress } from '../email';\nimport type { EmailMessage, EmailTransport } from '../email-sender';\nimport { signRequest } from './sigv4';\n\n/**\n * `@shware/workflow/transports/ses` — email through Amazon SES (the v2 `SendEmail` API) over\n * plain `fetch` with a SigV4 signature, so the same transport runs in a Lambda and in a Worker.\n * Nothing here loads the AWS SDK or a Node-only credential chain.\n */\n\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n /** Present for temporary credentials (a role's): signed with the request. */\n sessionToken?: string | undefined;\n}\n\n/** Called on every send, so rotated temporary credentials are picked up rather than frozen. */\nexport type AwsCredentialProvider = () => AwsCredentials | Promise<AwsCredentials>;\n\nexport interface SesEmailTransportOptions {\n region: string;\n /**\n * Fixed credentials, or a provider read on every send. Defaults to the process environment's\n * `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`, read at send time —\n * a Lambda's execution role. A Worker passes them explicitly from its validated env.\n */\n credentials?: AwsCredentials | AwsCredentialProvider;\n /** An SES configuration set (event publishing, a dedicated IP pool). */\n configurationSetName?: string;\n /** Defaults to `https://email.<region>.amazonaws.com`. */\n endpoint?: string;\n fetch?: typeof fetch;\n}\n\n/** An SES error response: `type` is SES's error code (`MessageRejected`, `TooManyRequestsException`, ...). */\nexport class SesSendError extends Error {\n override readonly name = 'SesSendError';\n constructor(\n readonly status: number,\n readonly type: string | undefined,\n message: string\n ) {\n super(`SES SendEmail failed (${status}${type === undefined ? '' : ` ${type}`}): ${message}`);\n }\n}\n\nfunction environmentCredentials(): AwsCredentials {\n // Read through globalThis: a Worker has no `process`, and must pass credentials instead\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n ?.env;\n const accessKeyId = env?.AWS_ACCESS_KEY_ID;\n const secretAccessKey = env?.AWS_SECRET_ACCESS_KEY;\n if (accessKeyId === undefined || secretAccessKey === undefined) {\n throw new Error(\n 'sesEmailTransport: no credentials — pass `credentials`, or run where AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set (a Lambda execution role)'\n );\n }\n return { accessKeyId, secretAccessKey, sessionToken: env?.AWS_SESSION_TOKEN };\n}\n\nexport function sesEmailTransport(options: SesEmailTransportOptions): EmailTransport {\n const endpoint = (options.endpoint ?? `https://email.${options.region}.amazonaws.com`).replace(\n /\\/$/,\n ''\n );\n const credentials = async (): Promise<AwsCredentials> => {\n const configured = options.credentials;\n if (configured === undefined) return environmentCredentials();\n return typeof configured === 'function' ? configured() : configured;\n };\n const fetchImpl = options.fetch ?? fetch;\n\n return {\n async send(message: EmailMessage) {\n const body = JSON.stringify({\n FromEmailAddress: mimeAddress(message.from),\n Destination: { ToAddresses: [message.to] },\n ...(message.replyTo === undefined\n ? {}\n : { ReplyToAddresses: [mimeAddress(message.replyTo)] }),\n Content: {\n Simple: {\n Subject: { Data: message.subject, Charset: 'UTF-8' },\n Body: { Html: { Data: message.html, Charset: 'UTF-8' } },\n },\n },\n ...(options.configurationSetName === undefined\n ? {}\n : { ConfigurationSetName: options.configurationSetName }),\n });\n const url = `${endpoint}/v2/email/outbound-emails`;\n const headers = await signRequest({\n method: 'POST',\n url,\n headers: { 'content-type': 'application/json' },\n body,\n credentials: await credentials(),\n region: options.region,\n service: 'ses',\n });\n // No retries here: the durable step owns them, and counts them — SES cannot de-duplicate,\n // so every extra attempt of an accepted send is another chance to deliver twice\n const response = await fetchImpl(url, { method: 'POST', headers, body });\n const text = await response.text();\n if (!response.ok) {\n let detail = text;\n try {\n const parsed = JSON.parse(text) as { message?: unknown; Message?: unknown };\n const said = parsed.message ?? parsed.Message;\n if (typeof said === 'string') detail = said;\n } catch {\n // Not JSON: the raw body says what went wrong\n }\n const type = response.headers.get('x-amzn-errortype')?.split(':')[0];\n throw new SesSendError(response.status, type, detail);\n }\n let messageId: unknown;\n try {\n messageId = (JSON.parse(text) as { MessageId?: unknown }).MessageId;\n } catch {\n messageId = undefined;\n }\n return typeof messageId === 'string' ? { providerMessageId: messageId } : {};\n },\n };\n}\n"],"mappings":";;;;AAoCA,IAAa,eAAb,cAAkC,MAAM;CAG3B;CACA;CAHX,OAAyB;CACzB,YACE,QACA,MACA,SACA;EACA,MAAM,yBAAyB,SAAS,SAAS,KAAA,IAAY,KAAK,IAAI,OAAO,KAAK,SAAS;EAJlF,KAAA,SAAA;EACA,KAAA,OAAA;CAIX;AACF;AAEA,SAAS,yBAAyC;CAEhD,MAAM,MAAO,WAA0E,SACnF;CACJ,MAAM,cAAc,KAAK;CACzB,MAAM,kBAAkB,KAAK;CAC7B,IAAI,gBAAgB,KAAA,KAAa,oBAAoB,KAAA,GACnD,MAAM,IAAI,MACR,oJACF;CAEF,OAAO;EAAE;EAAa;EAAiB,cAAc,KAAK;CAAkB;AAC9E;AAEA,SAAgB,kBAAkB,SAAmD;CACnF,MAAM,YAAY,QAAQ,YAAY,iBAAiB,QAAQ,OAAO,gBAAA,CAAiB,QACrF,OACA,EACF;CACA,MAAM,cAAc,YAAqC;EACvD,MAAM,aAAa,QAAQ;EAC3B,IAAI,eAAe,KAAA,GAAW,OAAO,uBAAuB;EAC5D,OAAO,OAAO,eAAe,aAAa,WAAW,IAAI;CAC3D;CACA,MAAM,YAAY,QAAQ,SAAS;CAEnC,OAAO,EACL,MAAM,KAAK,SAAuB;EAChC,MAAM,OAAO,KAAK,UAAU;GAC1B,kBAAkB,YAAY,QAAQ,IAAI;GAC1C,aAAa,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;GACzC,GAAI,QAAQ,YAAY,KAAA,IACpB,CAAC,IACD,EAAE,kBAAkB,CAAC,YAAY,QAAQ,OAAO,CAAC,EAAE;GACvD,SAAS,EACP,QAAQ;IACN,SAAS;KAAE,MAAM,QAAQ;KAAS,SAAS;IAAQ;IACnD,MAAM,EAAE,MAAM;KAAE,MAAM,QAAQ;KAAM,SAAS;IAAQ,EAAE;GACzD,EACF;GACA,GAAI,QAAQ,yBAAyB,KAAA,IACjC,CAAC,IACD,EAAE,sBAAsB,QAAQ,qBAAqB;EAC3D,CAAC;EACD,MAAM,MAAM,GAAG,SAAS;EACxB,MAAM,UAAU,MAAM,YAAY;GAChC,QAAQ;GACR;GACA,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C;GACA,aAAa,MAAM,YAAY;GAC/B,QAAQ,QAAQ;GAChB,SAAS;EACX,CAAC;EAGD,MAAM,WAAW,MAAM,UAAU,KAAK;GAAE,QAAQ;GAAQ;GAAS;EAAK,CAAC;EACvE,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI,CAAC,SAAS,IAAI;GAChB,IAAI,SAAS;GACb,IAAI;IACF,MAAM,SAAS,KAAK,MAAM,IAAI;IAC9B,MAAM,OAAO,OAAO,WAAW,OAAO;IACtC,IAAI,OAAO,SAAS,UAAU,SAAS;GACzC,QAAQ,CAER;GACA,MAAM,OAAO,SAAS,QAAQ,IAAI,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;GAClE,MAAM,IAAI,aAAa,SAAS,QAAQ,MAAM,MAAM;EACtD;EACA,IAAI;EACJ,IAAI;GACF,YAAa,KAAK,MAAM,IAAI,CAAC,CAA6B;EAC5D,QAAQ;GACN,YAAY,KAAA;EACd;EACA,OAAO,OAAO,cAAc,WAAW,EAAE,mBAAmB,UAAU,IAAI,CAAC;CAC7E,EACF;AACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/transports/sigv4.ts
|
|
3
|
+
const utf8 = new TextEncoder();
|
|
4
|
+
function hex(bytes) {
|
|
5
|
+
return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
6
|
+
}
|
|
7
|
+
async function sha256(data) {
|
|
8
|
+
return hex(await crypto.subtle.digest("SHA-256", utf8.encode(data)));
|
|
9
|
+
}
|
|
10
|
+
async function hmac(key, data) {
|
|
11
|
+
const imported = await crypto.subtle.importKey("raw", key, {
|
|
12
|
+
name: "HMAC",
|
|
13
|
+
hash: "SHA-256"
|
|
14
|
+
}, false, ["sign"]);
|
|
15
|
+
return crypto.subtle.sign("HMAC", imported, utf8.encode(data));
|
|
16
|
+
}
|
|
17
|
+
/** RFC 3986 encoding, as SigV4 wants it: only unreserved characters stay. */
|
|
18
|
+
function encode(value) {
|
|
19
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
20
|
+
}
|
|
21
|
+
/** Each path segment encoded once (the URL already decoded it). */
|
|
22
|
+
function canonicalPath(pathname) {
|
|
23
|
+
return pathname.split("/").map((segment) => encode(decodeURIComponent(segment))).join("/");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The query as sent, each name and value re-encoded. Parsed by hand: URLSearchParams reads `+`
|
|
27
|
+
* as a space, which would sign a value other than the one on the wire.
|
|
28
|
+
*/
|
|
29
|
+
function canonicalQuery(search) {
|
|
30
|
+
if (search === "" || search === "?") return "";
|
|
31
|
+
return search.slice(1).split("&").filter((pair) => pair !== "").map((pair) => {
|
|
32
|
+
const at = pair.indexOf("=");
|
|
33
|
+
const name = at === -1 ? pair : pair.slice(0, at);
|
|
34
|
+
const value = at === -1 ? "" : pair.slice(at + 1);
|
|
35
|
+
return [encode(decodeURIComponent(name)), encode(decodeURIComponent(value))];
|
|
36
|
+
}).sort(([a, av], [b, bv]) => a === b ? av < bv ? -1 : av > bv ? 1 : 0 : a < b ? -1 : 1).map(([name, value]) => `${name}=${value}`).join("&");
|
|
37
|
+
}
|
|
38
|
+
/** The request's headers with `authorization` (and `x-amz-date`, the session token) added. */
|
|
39
|
+
async function signRequest(request) {
|
|
40
|
+
const url = new URL(request.url);
|
|
41
|
+
const datetime = request.datetime ?? (/* @__PURE__ */ new Date()).toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
42
|
+
const date = datetime.slice(0, 8);
|
|
43
|
+
const { accessKeyId, secretAccessKey, sessionToken } = request.credentials;
|
|
44
|
+
const headers = {};
|
|
45
|
+
for (const [name, value] of Object.entries(request.headers ?? {})) headers[name.toLowerCase()] = value.trim().replace(/\s+/g, " ");
|
|
46
|
+
headers.host = url.host;
|
|
47
|
+
headers["x-amz-date"] = datetime;
|
|
48
|
+
if (sessionToken !== void 0) headers["x-amz-security-token"] = sessionToken;
|
|
49
|
+
const names = Object.keys(headers).sort();
|
|
50
|
+
const signedHeaders = names.join(";");
|
|
51
|
+
const canonical = [
|
|
52
|
+
request.method.toUpperCase(),
|
|
53
|
+
canonicalPath(url.pathname),
|
|
54
|
+
canonicalQuery(url.search),
|
|
55
|
+
...names.map((name) => `${name}:${headers[name]}`),
|
|
56
|
+
"",
|
|
57
|
+
signedHeaders,
|
|
58
|
+
await sha256(request.body ?? "")
|
|
59
|
+
].join("\n");
|
|
60
|
+
const scope = `${date}/${request.region}/${request.service}/aws4_request`;
|
|
61
|
+
const toSign = [
|
|
62
|
+
"AWS4-HMAC-SHA256",
|
|
63
|
+
datetime,
|
|
64
|
+
scope,
|
|
65
|
+
await sha256(canonical)
|
|
66
|
+
].join("\n");
|
|
67
|
+
let key = await hmac(utf8.encode(`AWS4${secretAccessKey}`), date);
|
|
68
|
+
for (const part of [
|
|
69
|
+
request.region,
|
|
70
|
+
request.service,
|
|
71
|
+
"aws4_request"
|
|
72
|
+
]) key = await hmac(key, part);
|
|
73
|
+
const signature = hex(await hmac(key, toSign));
|
|
74
|
+
const { host: _host, ...sent } = headers;
|
|
75
|
+
return {
|
|
76
|
+
...sent,
|
|
77
|
+
authorization: `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
exports.signRequest = signRequest;
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=sigv4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.cjs","names":[],"sources":["../../src/transports/sigv4.ts"],"sourcesContent":["/**\n * AWS Signature Version 4 over WebCrypto, which a Worker and Node both have: enough to sign\n * the JSON API calls the transports make. Headers are signed as given (plus `host` and\n * `x-amz-date`); the payload hash is the body's SHA-256.\n * https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html\n */\n\nexport interface SigV4Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string | undefined;\n}\n\nexport interface SigV4Request {\n method: string;\n url: string;\n headers?: Record<string, string>;\n body?: string;\n credentials: SigV4Credentials;\n region: string;\n service: string;\n /** `YYYYMMDD'T'HHMMSS'Z'`; now by default. */\n datetime?: string;\n}\n\nconst utf8 = new TextEncoder();\n\nfunction hex(bytes: ArrayBuffer): string {\n return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nasync function sha256(data: string): Promise<string> {\n return hex(await crypto.subtle.digest('SHA-256', utf8.encode(data)));\n}\n\nasync function hmac(\n key: ArrayBuffer | Uint8Array<ArrayBuffer>,\n data: string\n): Promise<ArrayBuffer> {\n const imported = await crypto.subtle.importKey(\n 'raw',\n key,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n return crypto.subtle.sign('HMAC', imported, utf8.encode(data));\n}\n\n/** RFC 3986 encoding, as SigV4 wants it: only unreserved characters stay. */\nfunction encode(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`\n );\n}\n\n/** Each path segment encoded once (the URL already decoded it). */\nfunction canonicalPath(pathname: string): string {\n return pathname\n .split('/')\n .map((segment) => encode(decodeURIComponent(segment)))\n .join('/');\n}\n\n/**\n * The query as sent, each name and value re-encoded. Parsed by hand: URLSearchParams reads `+`\n * as a space, which would sign a value other than the one on the wire.\n */\nfunction canonicalQuery(search: string): string {\n if (search === '' || search === '?') return '';\n return search\n .slice(1)\n .split('&')\n .filter((pair) => pair !== '')\n .map((pair) => {\n const at = pair.indexOf('=');\n const name = at === -1 ? pair : pair.slice(0, at);\n const value = at === -1 ? '' : pair.slice(at + 1);\n return [encode(decodeURIComponent(name)), encode(decodeURIComponent(value))] as const;\n })\n .sort(([a, av], [b, bv]) => (a === b ? (av < bv ? -1 : av > bv ? 1 : 0) : a < b ? -1 : 1))\n .map(([name, value]) => `${name}=${value}`)\n .join('&');\n}\n\n/** The request's headers with `authorization` (and `x-amz-date`, the session token) added. */\nexport async function signRequest(request: SigV4Request): Promise<Record<string, string>> {\n const url = new URL(request.url);\n const datetime = request.datetime ?? new Date().toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n const date = datetime.slice(0, 8);\n const { accessKeyId, secretAccessKey, sessionToken } = request.credentials;\n\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(request.headers ?? {})) {\n headers[name.toLowerCase()] = value.trim().replace(/\\s+/g, ' ');\n }\n headers.host = url.host;\n headers['x-amz-date'] = datetime;\n if (sessionToken !== undefined) headers['x-amz-security-token'] = sessionToken;\n\n const names = Object.keys(headers).sort();\n const signedHeaders = names.join(';');\n const canonical = [\n request.method.toUpperCase(),\n canonicalPath(url.pathname),\n canonicalQuery(url.search),\n ...names.map((name) => `${name}:${headers[name]}`),\n '',\n signedHeaders,\n await sha256(request.body ?? ''),\n ].join('\\n');\n\n const scope = `${date}/${request.region}/${request.service}/aws4_request`;\n const toSign = ['AWS4-HMAC-SHA256', datetime, scope, await sha256(canonical)].join('\\n');\n\n let key = await hmac(utf8.encode(`AWS4${secretAccessKey}`), date);\n for (const part of [request.region, request.service, 'aws4_request']) {\n key = await hmac(key, part);\n }\n const signature = hex(await hmac(key, toSign));\n\n const { host: _host, ...sent } = headers;\n return {\n ...sent,\n authorization: `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,\n };\n}\n"],"mappings":";;AAyBA,MAAM,OAAO,IAAI,YAAY;AAE7B,SAAS,IAAI,OAA4B;CACvC,OAAO,CAAC,GAAG,IAAI,WAAW,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AACvF;AAEA,eAAe,OAAO,MAA+B;CACnD,OAAO,IAAI,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK,OAAO,IAAI,CAAC,CAAC;AACrE;AAEA,eAAe,KACb,KACA,MACsB;CACtB,MAAM,WAAW,MAAM,OAAO,OAAO,UACnC,OACA,KACA;EAAE,MAAM;EAAQ,MAAM;CAAU,GAChC,OACA,CAAC,MAAM,CACT;CACA,OAAO,OAAO,OAAO,KAAK,QAAQ,UAAU,KAAK,OAAO,IAAI,CAAC;AAC/D;;AAGA,SAAS,OAAO,OAAuB;CACrC,OAAO,mBAAmB,KAAK,CAAC,CAAC,QAC/B,aACC,MAAM,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,GACtD;AACF;;AAGA,SAAS,cAAc,UAA0B;CAC/C,OAAO,SACJ,MAAM,GAAG,CAAC,CACV,KAAK,YAAY,OAAO,mBAAmB,OAAO,CAAC,CAAC,CAAC,CACrD,KAAK,GAAG;AACb;;;;;AAMA,SAAS,eAAe,QAAwB;CAC9C,IAAI,WAAW,MAAM,WAAW,KAAK,OAAO;CAC5C,OAAO,OACJ,MAAM,CAAC,CAAC,CACR,MAAM,GAAG,CAAC,CACV,QAAQ,SAAS,SAAS,EAAE,CAAC,CAC7B,KAAK,SAAS;EACb,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,MAAM,OAAO,OAAO,KAAK,OAAO,KAAK,MAAM,GAAG,EAAE;EAChD,MAAM,QAAQ,OAAO,KAAK,KAAK,KAAK,MAAM,KAAK,CAAC;EAChD,OAAO,CAAC,OAAO,mBAAmB,IAAI,CAAC,GAAG,OAAO,mBAAmB,KAAK,CAAC,CAAC;CAC7E,CAAC,CAAC,CACD,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,QAAS,MAAM,IAAK,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,IAAK,IAAI,IAAI,KAAK,CAAE,CAAC,CACzF,KAAK,CAAC,MAAM,WAAW,GAAG,KAAK,GAAG,OAAO,CAAC,CAC1C,KAAK,GAAG;AACb;;AAGA,eAAsB,YAAY,SAAwD;CACxF,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;CAC/B,MAAM,WAAW,QAAQ,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,CAAC,QAAQ,iBAAiB,EAAE;CACzF,MAAM,OAAO,SAAS,MAAM,GAAG,CAAC;CAChC,MAAM,EAAE,aAAa,iBAAiB,iBAAiB,QAAQ;CAE/D,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,GAC9D,QAAQ,KAAK,YAAY,KAAK,MAAM,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG;CAEhE,QAAQ,OAAO,IAAI;CACnB,QAAQ,gBAAgB;CACxB,IAAI,iBAAiB,KAAA,GAAW,QAAQ,0BAA0B;CAElE,MAAM,QAAQ,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK;CACxC,MAAM,gBAAgB,MAAM,KAAK,GAAG;CACpC,MAAM,YAAY;EAChB,QAAQ,OAAO,YAAY;EAC3B,cAAc,IAAI,QAAQ;EAC1B,eAAe,IAAI,MAAM;EACzB,GAAG,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,QAAQ,OAAO;EACjD;EACA;EACA,MAAM,OAAO,QAAQ,QAAQ,EAAE;CACjC,CAAC,CAAC,KAAK,IAAI;CAEX,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,OAAO,GAAG,QAAQ,QAAQ;CAC3D,MAAM,SAAS;EAAC;EAAoB;EAAU;EAAO,MAAM,OAAO,SAAS;CAAC,CAAC,CAAC,KAAK,IAAI;CAEvF,IAAI,MAAM,MAAM,KAAK,KAAK,OAAO,OAAO,iBAAiB,GAAG,IAAI;CAChE,KAAK,MAAM,QAAQ;EAAC,QAAQ;EAAQ,QAAQ;EAAS;CAAc,GACjE,MAAM,MAAM,KAAK,KAAK,IAAI;CAE5B,MAAM,YAAY,IAAI,MAAM,KAAK,KAAK,MAAM,CAAC;CAE7C,MAAM,EAAE,MAAM,OAAO,GAAG,SAAS;CACjC,OAAO;EACL,GAAG;EACH,eAAe,+BAA+B,YAAY,GAAG,MAAM,kBAAkB,cAAc,cAAc;CACnH;AACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/transports/sigv4.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* AWS Signature Version 4 over WebCrypto, which a Worker and Node both have: enough to sign
|
|
4
|
+
* the JSON API calls the transports make. Headers are signed as given (plus `host` and
|
|
5
|
+
* `x-amz-date`); the payload hash is the body's SHA-256.
|
|
6
|
+
* https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
|
|
7
|
+
*/
|
|
8
|
+
export interface SigV4Credentials {
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
secretAccessKey: string;
|
|
11
|
+
sessionToken?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface SigV4Request {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
body?: string;
|
|
18
|
+
credentials: SigV4Credentials;
|
|
19
|
+
region: string;
|
|
20
|
+
service: string;
|
|
21
|
+
/** `YYYYMMDD'T'HHMMSS'Z'`; now by default. */
|
|
22
|
+
datetime?: string;
|
|
23
|
+
}
|
|
24
|
+
/** The request's headers with `authorization` (and `x-amz-date`, the session token) added. */
|
|
25
|
+
export declare function signRequest(request: SigV4Request): Promise<Record<string, string>>;
|
|
26
|
+
//#endregion
|
|
27
|
+
//# sourceMappingURL=sigv4.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.cts","names":[],"sources":["../../src/transports/sigv4.ts"],"mappings":";;;;;;;iBAOiB;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA,UAAU;EACV;EACA,aAAa;EACb;EACA;;EAEA;;;wBAiEoB,YAAY,SAAS,eAAe,QAAQ"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/transports/sigv4.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* AWS Signature Version 4 over WebCrypto, which a Worker and Node both have: enough to sign
|
|
4
|
+
* the JSON API calls the transports make. Headers are signed as given (plus `host` and
|
|
5
|
+
* `x-amz-date`); the payload hash is the body's SHA-256.
|
|
6
|
+
* https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
|
|
7
|
+
*/
|
|
8
|
+
export interface SigV4Credentials {
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
secretAccessKey: string;
|
|
11
|
+
sessionToken?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface SigV4Request {
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
body?: string;
|
|
18
|
+
credentials: SigV4Credentials;
|
|
19
|
+
region: string;
|
|
20
|
+
service: string;
|
|
21
|
+
/** `YYYYMMDD'T'HHMMSS'Z'`; now by default. */
|
|
22
|
+
datetime?: string;
|
|
23
|
+
}
|
|
24
|
+
/** The request's headers with `authorization` (and `x-amz-date`, the session token) added. */
|
|
25
|
+
export declare function signRequest(request: SigV4Request): Promise<Record<string, string>>;
|
|
26
|
+
//#endregion
|
|
27
|
+
//# sourceMappingURL=sigv4.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.mts","names":[],"sources":["../../src/transports/sigv4.ts"],"mappings":";;;;;;;iBAOiB;EACf;EACA;EACA;;iBAGe;EACf;EACA;EACA,UAAU;EACV;EACA,aAAa;EACb;EACA;;EAEA;;;wBAiEoB,YAAY,SAAS,eAAe,QAAQ"}
|