1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isBindingHost } from "../Lambda/Function.ts";
4import type { LinkedWhatsAppBusinessAccount } from "./LinkedWhatsAppBusinessAccount.ts";
5
6/**
7 * Shared scaffolding for AWS End User Messaging Social (WhatsApp) HTTP
8 * bindings.
9 *
10 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service
11 * is a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
12 * builders below. Everything except the operation and the IAM action list
13 * is boilerplate: WABA-scoped bindings inject the bound linked account's
14 * `id` (`waba-…`) into every request and grant `actions` on the WABA ARN;
15 * phone-plane bindings pass the request through (the caller addresses a
16 * phone number by its `phone-number-id-…`) and grant `actions` on `*`,
17 * because the WABA's phone numbers are provisioned by Meta at onboarding
18 * time and their ARNs are not derivable from the WABA ARN at bind time.
19 */
20
21/**
22 * Build the impl Effect for an operation scoped to the bound
23 * {@link LinkedWhatsAppBusinessAccount} (message templates, WhatsApp
24 * Flows): the deploy-time half grants `actions` on the WABA ARN and the
25 * runtime half injects the linked account id into every request as `id`.
26 */
27export const makeWabaScopedHttpBinding = <
28 I extends { id: string },
29 A,
30 E,
31 R,
32>(options: {
33 /** Fully-qualified binding tag, e.g. `AWS.SocialMessaging.ListWhatsAppMessageTemplates`. */
34 tag: string;
35 /** The distilled operation; `id` is injected from the linked account. */
36 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
37 /** IAM actions granted on the WABA ARN. */
38 actions: readonly string[];
39}) =>
40 Effect.gen(function* () {
41 const op = yield* options.operation;
42
43 return Effect.fn(function* (account: LinkedWhatsAppBusinessAccount) {
44 // Output yields a DEFERRED effect — resolve again per invocation below.
45 const AccountId = yield* account.id;
46 if (!globalThis.__ALCHEMY_RUNTIME__) {
47 const host = yield* Binding.Host;
48 if (isBindingHost(host)) {
49 yield* host.bind`Allow(${host}, ${options.tag}(${account}))`({
50 policyStatements: [
51 {
52 Effect: "Allow",
53 Action: [...options.actions],
54 Resource: [account.arn],
55 },
56 ],
57 });
58 }
59 }
60 return Effect.fn(`${options.tag}(${account.LogicalId})`)(function* (
61 request?: Omit<I, "id">,
62 ) {
63 return yield* op({ ...request, id: yield* AccountId } as I);
64 });
65 });
66 });
67
68/**
69 * Build the impl Effect for a phone-number-plane operation
70 * (`SendWhatsAppMessage`, message media): the caller addresses one of the
71 * bound WABA's phone numbers per request (`originationPhoneNumberId` /
72 * `id`), so the request passes through unchanged. The deploy-time half
73 * grants `actions` on `*` — phone numbers are provisioned by Meta under
74 * the WABA and their ARNs are not derivable from the WABA ARN.
75 */
76export const makeWabaPhonePlaneHttpBinding = <I, A, E, R>(options: {
77 /** Fully-qualified binding tag, e.g. `AWS.SocialMessaging.SendWhatsAppMessage`. */
78 tag: string;
79 /** The distilled operation, invoked with the caller's request as-is. */
80 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
81 /** IAM actions granted on `*`. */
82 actions: readonly string[];
83}) =>
84 Effect.gen(function* () {
85 const op = yield* options.operation;
86
87 return Effect.fn(function* (account: LinkedWhatsAppBusinessAccount) {
88 if (!globalThis.__ALCHEMY_RUNTIME__) {
89 const host = yield* Binding.Host;
90 if (isBindingHost(host)) {
91 yield* host.bind`Allow(${host}, ${options.tag}(${account}))`({
92 policyStatements: [
93 {
94 Effect: "Allow",
95 Action: [...options.actions],
96 Resource: ["*"],
97 },
98 ],
99 });
100 }
101 }
102 return Effect.fn(`${options.tag}(${account.LogicalId})`)(function* (
103 request: I,
104 ) {
105 return yield* op(request);
106 });
107 });
108 });