1import type {
2 GetTunnelCloudflaredConfigurationError,
3 GetTunnelCloudflaredConfigurationResponse,
4 GetTunnelCloudflaredError,
5 GetTunnelCloudflaredResponse,
6 GetTunnelCloudflaredTokenError,
7 GetTunnelCloudflaredTokenResponse,
8 ListTunnelCloudflaredsError,
9 ListTunnelCloudflaredsRequest,
10 ListTunnelCloudflaredsResponse,
11} from "@distilled.cloud/cloudflare/zero-trust";
12import * as zeroTrust from "@distilled.cloud/cloudflare/zero-trust";
13import * as Effect from "effect/Effect";
14import * as Binding from "../../Binding.ts";
15import type { Worker } from "../Workers/Worker.ts";
16import type { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
17import type { RuntimeContext } from "../../RuntimeContext.ts";
18import { type TunnelAuth } from "./TunnelBinding.ts";
19
20/**
21 * Binding that lets a Worker read Cloudflare Tunnels at runtime.
22 *
23 * Creates a scoped {@link AccountApiToken} with only the `Cloudflare Tunnel
24 * Read` permission and binds its outputs into the Worker (the token value as a
25 * `secret_text` binding) so runtime code can authenticate.
26 *
27 * @remarks
28 *
29 * `ReadTunnel` is a single identifier that is simultaneously the binding's
30 * Context tag, its type, and the callable — `yield* Cloudflare.Tunnel.ReadTunnel()`.
31 *
32 * ### Reading tunnels at runtime
33 * **Example:** Bind the read client
34 * Bind once in the Init phase; every method is available on the returned client.
35 * ```typescript
36 * const tunnels = yield* Cloudflare.Tunnel.ReadTunnel();
37 * ```
38 *
39 * **Example:** List tunnels
40 * ```typescript
41 * const { result } = yield* tunnels.list({ isDeleted: false });
42 * ```
43 *
44 * **Example:** Fetch a tunnel and its connector token
45 * `getToken` returns the plaintext token used to run `cloudflared`.
46 * ```typescript
47 * const tunnel = yield* tunnels.get(tunnelId);
48 * const token = yield* tunnels.getToken(tunnelId);
49 * ```
50 *
51 * **Example:** Read the ingress configuration
52 * ```typescript
53 * const { config } = yield* tunnels.getConfiguration(tunnelId);
54 * ```
55 *
56 * ### Runtime Layer
57 * Provide {@link ReadTunnelBinding} in the Worker's runtime layer.
58 * ```typescript
59 * Effect.provide(Cloudflare.Tunnel.ReadTunnelBinding)
60 * ```
61 *
62 * @binding
63 * @product Tunnels
64 * @category Cloudflare One (Zero Trust)
65 */
66export interface ReadTunnel extends Binding.Service<
67 ReadTunnel,
68 "Cloudflare.Tunnel.ReadTunnel",
69 () => Effect.Effect<ReadTunnelClient, never, Worker | CloudflareEnvironment>
70> {}
71
72export const ReadTunnel = Binding.Service<ReadTunnel>(
73 "Cloudflare.Tunnel.ReadTunnel",
74);
75
76/** List-tunnels request, minus the account id (supplied by the binding). */
77export type ListTunnelsRequest = Omit<
78 ListTunnelCloudflaredsRequest,
79 "accountId"
80>;
81
82/**
83 * Read-only tunnel operations. Backed by the `Cloudflare Tunnel Read`
84 * permission group.
85 */
86export interface ReadTunnelClient {
87 /** Fetch a single tunnel by id. */
88 get(
89 tunnelId: string,
90 ): Effect.Effect<
91 GetTunnelCloudflaredResponse,
92 GetTunnelCloudflaredError,
93 RuntimeContext
94 >;
95 /** List tunnels in the account. */
96 list(
97 request?: ListTunnelsRequest,
98 ): Effect.Effect<
99 ListTunnelCloudflaredsResponse,
100 ListTunnelCloudflaredsError,
101 RuntimeContext
102 >;
103 /** Fetch the connector token used to run the tunnel. */
104 getToken(
105 tunnelId: string,
106 ): Effect.Effect<
107 GetTunnelCloudflaredTokenResponse,
108 GetTunnelCloudflaredTokenError,
109 RuntimeContext
110 >;
111 /** Read the remotely-managed configuration (ingress rules) for a tunnel. */
112 getConfiguration(
113 tunnelId: string,
114 ): Effect.Effect<
115 GetTunnelCloudflaredConfigurationResponse,
116 GetTunnelCloudflaredConfigurationError,
117 RuntimeContext
118 >;
119}
120
121/** Build the read-only client over an injectable {@link TunnelAuth}. */
122export const readClient = (auth: TunnelAuth): ReadTunnelClient => {
123 const { authorize } = auth;
124 return {
125 get: Effect.fn("Cloudflare.Tunnel.get")(function* (tunnelId) {
126 const accountId = yield* auth.accountId;
127 return yield* authorize(
128 zeroTrust.getTunnelCloudflared({ accountId, tunnelId }),
129 );
130 }),
131 list: Effect.fn("Cloudflare.Tunnel.list")(function* (request) {
132 const accountId = yield* auth.accountId;
133 return yield* authorize(
134 zeroTrust.listTunnelCloudflareds({ accountId, ...request }),
135 );
136 }),
137 getToken: Effect.fn("Cloudflare.Tunnel.getToken")(function* (tunnelId) {
138 const accountId = yield* auth.accountId;
139 return yield* authorize(
140 zeroTrust.getTunnelCloudflaredToken({ accountId, tunnelId }),
141 );
142 }),
143 getConfiguration: Effect.fn("Cloudflare.Tunnel.getConfiguration")(
144 function* (tunnelId) {
145 const accountId = yield* auth.accountId;
146 return yield* authorize(
147 zeroTrust.getTunnelCloudflaredConfiguration({ accountId, tunnelId }),
148 );
149 },
150 ),
151 };
152};