All files / src/core/bindings VpcNetworkBinding.ts

100% Statements 8/8
100% Branches 0/0
100% Functions 4/4
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111              28x 28x                                                                                                           28x               9x   9x                                   2x                                 3x      
/**
 * @module
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.3.0
 */
import { Binding } from '@/core/bindings/Binding'
import { lazySettings } from '@/utils/lazySettings'
import { VpcNetworkSettings } from '@/types/VpcNetworkSettings'
 
import type { VpcNetwork } from '@/types/VpcNetwork'
import type { CloudflareEnv } from '@/types/CloudflareEnv'
 
/**
 * Accessor for a VPC Network binding.
 *
 * @remarks
 * The broad half of Workers VPC. Where {@link VpcServiceBinding} is pinned to
 * one private host and port at deploy time, this opens a whole network — a
 * Cloudflare Tunnel by `tunnel_id`, or Cloudflare Mesh by `network_id` — and
 * lets each call name its destination. Nothing has to be registered in advance,
 * which is what makes it usable for a private network whose addresses are not
 * known when the Worker ships.
 *
 * That reach is also the reason to prefer a VPC Service when one will do. A
 * Worker holding this binding can reach anything on the network, so a request
 * that influences the address it calls is a server-side request forgery with a
 * private network behind it. Cloudflare's own answer to constraining that is a
 * VPC Service per destination, not a filter — and see
 * {@link VpcNetworkSettings} for why this package does not offer one.
 *
 * ## Two protocols
 *
 * {@link fetch} for HTTP, {@link connect} for everything that is not: Redis,
 * Memcached, MQTT, anything speaking its own bytes. `connect` is plaintext TCP
 * at the time of writing, so a private service expecting TLS needs it
 * terminated on the private side.
 *
 * ## This one names a type this package declares
 *
 * Unique here, and not by preference. `@cloudflare/workers-types` declares
 * nothing for Workers VPC at the pinned version, and unlike a VPC Service —
 * which is `Fetcher` structurally — a network binding's `connect` has no
 * existing type to borrow. {@link VpcNetwork} is therefore written here, kept
 * to the two documented methods, and meant to be deleted when the real type
 * ships. See that interface for what it costs.
 *
 * @example
 * ```ts
 * const network = new VpcNetworkBinding()
 *
 * const response = await network.fetch(env, 'http://10.0.1.50:8080/api/data')
 * const socket = await network.connect(env, '10.0.1.50:6379')
 * ```
 *
 * @class
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.3.0
 */
export class VpcNetworkBinding<E extends CloudflareEnv = CloudflareEnv> extends Binding<VpcNetwork, E> {
  /**
   * Constructs a VpcNetworkBinding instance.
   *
   * @param settings Resolved settings. Defaults to `resolve('vpcNetwork')`, so callers
   *   normally construct this with no arguments at all.
   */
  constructor(settings?: VpcNetworkSettings) {
    const settle = lazySettings<VpcNetworkSettings>('vpcNetwork', settings)
 
    super(() => settle().binding)
  }
 
  /**
   * Sends a request to a destination on the private network.
   *
   * @remarks
   * The URL decides the destination, so it is the argument that must never come
   * from a request this Worker is serving. Any hostname or address reachable
   * through the bound tunnel or mesh is in range.
   *
   * @param env The Worker environment.
   * @param resource The private destination, as a URL, string, or `Request`.
   * @param options Standard request options.
   * @returns The response from the private service.
   * @throws {@link MissingBindingError} When the VPC Network binding is absent.
   */
  public async fetch(env: E, resource: string | URL | Request, options?: RequestInit): Promise<Response> {
    return await this.resolve(env).fetch(resource, options)
  }
 
  /**
   * Opens a TCP socket to a destination on the private network.
   *
   * @remarks
   * The caller owns the socket from here — closing it, and draining both sides.
   * Nothing in this package wraps that, because a socket's lifetime belongs to
   * the protocol being spoken over it and this class knows no protocol.
   *
   * @param env The Worker environment.
   * @param address The private destination, as `host:port` or a `SocketAddress`.
   * @returns The connected socket.
   * @throws {@link MissingBindingError} When the VPC Network binding is absent.
   */
  public async connect(env: E, address: string | SocketAddress): Promise<Socket> {
    return await this.resolve(env).connect(address)
  }
}