# core/adapters/site.adapter.ts

> 60 lines of code and 17 definitions.

Tree: Bane's Lab Deploy
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/deploy#file-deploy-core-adapters-site-adapter-ts
Source text: https://banes-lab.com/source/deploy/core/adapters/site.adapter.ts.txt

Listed in [core/adapters](https://banes-lab.com/api/source/deploy/core/adapters.md), after [core/adapters/shell.adapter.ts](https://banes-lab.com/source/deploy/core/adapters/shell.adapter.ts.md) and before [core/adapters/webhook.adapter.ts](https://banes-lab.com/source/deploy/core/adapters/webhook.adapter.ts.md).

## Definitions

- `servedKey` (lexical_declaration, line 28)
- `BODY_METHOD` (lexical_declaration, line 17)
- `HEAD_METHOD` (lexical_declaration, line 18)
- `TYPE_HEADER` (lexical_declaration, line 19)
- `DIGEST` (lexical_declaration, line 20)
- `HEX` (lexical_declaration, line 21)
- `keyDigestOf` (lexical_declaration, line 23)
- `spki` (lexical_declaration, line 24)
- `call` (lexical_declaration, line 30)
- `{ socket }` (lexical_declaration, line 31)
- `readDane` (lexical_declaration, line 47, exported)
- `served` (lexical_declaration, line 48, exported)
- `name` (lexical_declaration, line 49, exported)
- `published` (lexical_declaration, line 50, exported)
- `fetchProbe` (lexical_declaration, line 61, exported)
- `response` (lexical_declaration, line 62, exported)
- `body` (lexical_declaration, line 63, exported)

## Contained in

- [core/adapters](https://banes-lab.com/anatomy/deploy/folder-deploy-core-adapters.md)

## Uses

- [configuration/strings/deployment.strings.ts](https://banes-lab.com/source/deploy/configuration/strings/deployment.strings.ts.md)

## Grounds

- [Timeout Pattern](https://banes-lab.com/records/architecture/timeout-pattern.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/deploy/folder-deploy-configuration-strings.md)

## Source

```typescript
import {
    DANE_END_ENTITY,
    DANE_PUBLIC_KEY,
    DANE_SHA256,
    PROBE_TIMEOUT_MS,
    TLSA_PORT_MARK,
    TLSA_PROTOCOL,
} from "#configuration/constants/deployment.constants";
import type { DaneKeys, ProbeResponse, ServedKey } from "#types/deployment.types";
import { X509Certificate, createHash } from "node:crypto";
import { tlsMissing, tlsTimeout } from "#configuration/strings/deployment.strings";
import { Buffer } from "node:buffer";
import { TLSSocket } from "node:tls";
import { request } from "node:https";
import { resolveTlsa } from "node:dns/promises";

const BODY_METHOD = "GET";
const HEAD_METHOD = "HEAD";
const TYPE_HEADER = "content-type";
const DIGEST = "sha256";
const HEX = "hex";

const keyDigestOf = function keyDigestOf(raw: Buffer): string {
    const spki = new X509Certificate(raw).publicKey.export({ format: "der", type: "spki" });
    return createHash(DIGEST).update(spki).digest(HEX);
};

const servedKey = async function servedKey(site: string): Promise<ServedKey> {
    return new Promise((resolve, reject) => {
        const call = request(site, { method: HEAD_METHOD, timeout: PROBE_TIMEOUT_MS }, (response) => {
            const { socket } = response;
            response.resume();
            if (!(socket instanceof TLSSocket) || socket.remotePort === undefined) {
                reject(new Error(tlsMissing(site)));
                return;
            }
            resolve({ digest: keyDigestOf(socket.getPeerCertificate().raw), port: socket.remotePort });
        });
        call.on("timeout", () => {
            call.destroy(new Error(tlsTimeout(site)));
        });
        call.on("error", reject);
        call.end();
    });
};

export const readDane = async function readDane(site: string): Promise<DaneKeys> {
    const served = await servedKey(site);
    const name = TLSA_PORT_MARK + String(served.port) + TLSA_PROTOCOL + new URL(site).hostname;
    const published = (await resolveTlsa(name))
        .filter(
            (record) =>
                record.certUsage === DANE_END_ENTITY &&
                record.selector === DANE_PUBLIC_KEY &&
                record.match === DANE_SHA256,
        )
        .map((record) => Buffer.from(record.data).toString(HEX));
    return { name, published, served: served.digest };
};

export const fetchProbe = async function fetchProbe(url: string, method: string): Promise<ProbeResponse> {
    const response = await fetch(url, { method, redirect: "manual", signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
    const body = method === BODY_METHOD ? await response.text() : "";
    return { body, status: response.status, type: response.headers.get(TYPE_HEADER) ?? "" };
};
```
