# HTTP Client, Readiness Probe & Authentication

> The api.js module: makeRequest() signature, Bearer token injection, JSON vs text response handling, and the waitForApp() readiness loop (60 s MAX_WAIT_TIME). The auth.js module: the two-phase authenticate() flow — initializeAdmin() via POST /mr-api/initialize, falling back to login() via POST /mr-api/login when the system is already bootstrapped. Token extraction and propagation to downstream calls.

- Repository: vtex/dk-flags
- GitHub: https://github.com/vtex/dk-flags
- Human wiki: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa
- Complete Markdown: https://grok-wiki.com/public/wiki/vtex-dk-flags-0a8c140c3cfa/llms-full.txt

## Source Files

- `docker/api.js`
- `docker/auth.js`
- `docker/config.js`
- `docker/setup.js`
- `specs/dk-flags-party-server-image.md`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docker/api.js](docker/api.js)
- [docker/auth.js](docker/auth.js)
- [docker/config.js](docker/config.js)
- [docker/setup.js](docker/setup.js)
- [specs/dk-flags-party-server-image.md](specs/dk-flags-party-server-image.md)
</details>

# HTTP Client, Readiness Probe & Authentication

The `docker/api.js` and `docker/auth.js` modules form the foundation layer of the `dk-flags` build-time initialization pipeline. Together they handle every outbound HTTP call, gate the rest of setup on a confirmed-ready server, and obtain the Bearer token that all subsequent FeatureHub Management API calls require. Because the entire pipeline runs at **image build time** (inside `docker build`, not on container start), reliability and deterministic failure behavior matter more than runtime flexibility.

This page covers the function signatures, request-construction rules, response-parsing strategy, readiness-probe timing, and the two-phase authentication flow in full detail.

---

## Module Overview

```text
config.js
 ├─ APP_URL, MAX_WAIT_TIME, ADMIN_*, PORTFOLIO_NAME …
 │
api.js  (imports config)
 ├─ makeRequest(method, path, data?, token?) → { status, data, headers }
 └─ waitForApp()                             → Promise<true> | throws
 │
auth.js  (imports config + api)
 ├─ initializeAdmin()  → { success, token }
 ├─ login()            → { success, token }
 └─ authenticate()     → Promise<token | null>
 │
setup.js  (imports api + auth + portfolio + features + flags)
 └─ orchestrator: waitForApp → authenticate → … → createAllFeatures
```

Sources: [docker/api.js:1-65](), [docker/auth.js:1-84](), [docker/config.js:1-10](), [docker/setup.js:1-82]()

---

## `api.js` — HTTP Client

### `makeRequest()` Signature

```js
// docker/api.js:3
async function makeRequest(method, path, data = null, token = null)
```

| Parameter | Type | Required | Notes |
|-----------|------|----------|-------|
| `method`  | string | yes | `'GET'`, `'POST'`, `'PUT'`, etc. |
| `path`    | string | yes | Relative path; resolved against `config.APP_URL` via `new URL(path, base)` |
| `data`    | object \| null | no | Serialized as JSON body when non-null |
| `token`   | string \| null | no | When set, injects `Authorization: Bearer <token>` |

Sources: [docker/api.js:3-10]()

### Request Construction

The function always sets `Content-Type: application/json`. The target URL is assembled with the native `URL` constructor, which means `path` must be a relative or absolute path string — not a full URL — when a base is already defined in `config.APP_URL`.

```js
// docker/api.js:4-15
const url = new URL(path, config.APP_URL);

const options = {
    method,
    headers: { 'Content-Type': 'application/json' }
};

if (token) {
    options.headers['Authorization'] = `Bearer ${token}`;
}

if (data) {
    options.body = JSON.stringify(data);
}
```

### Bearer Token Injection

Token injection is opt-in per call: `makeRequest` receives the token as an argument, checks for truthiness, and appends the `Authorization` header only when a token is present. The token is never stored in module state — callers (typically `auth.js` and the higher-level modules) thread it through explicitly.

Sources: [docker/api.js:13-15]()

### Response Handling: JSON vs. Text

After `fetch` resolves, `makeRequest` inspects the `content-type` response header to decide how to decode the body:

```js
// docker/api.js:23-30
const contentType = response.headers.get('content-type');
let responseData = null;

if (contentType && contentType.includes('application/json')) {
    responseData = await response.json();
} else {
    responseData = await response.text();
}
```

The resolved response envelope is:

```js
{ status: response.status, data: responseData, headers: Object.fromEntries(response.headers.entries()) }
```

`status` is always the numeric HTTP status code. `data` is a parsed object for JSON responses, a plain string for everything else. `headers` is a plain object (not `Headers`) so callers can destructure without the `Headers` API.

Sources: [docker/api.js:22-36]()

### Error Behavior

Network-level errors (fetch throws) are caught and re-thrown with the message prefix `"Request failed: …"`. HTTP error status codes (4xx, 5xx) are **not** thrown — they are returned as `{ status: N, data: … }` and the caller decides whether the status is acceptable.

Sources: [docker/api.js:37-39]()

---

## `api.js` — Readiness Probe: `waitForApp()`

### Behavior

`waitForApp()` issues plain `GET ${config.APP_URL}` requests in a 1-second polling loop until either the response is `ok` (HTTP 2xx) or the elapsed time exceeds `MAX_WAIT_TIME`.

```js
// docker/api.js:42-59
async function waitForApp() {
    console.log(`Waiting for app to be ready at ${config.APP_URL}...`);
    const startTime = Date.now();

    while (Date.now() - startTime < config.MAX_WAIT_TIME) {
        try {
            const response = await fetch(config.APP_URL);
            if (response.ok) {
                console.log('App is ready!');
                return true;
            }
        } catch (error) {
            // App not ready yet, continue waiting
        }
        await new Promise(resolve => setTimeout(resolve, 1000));
    }

    throw new Error('App did not become ready within the timeout period');
}
```

### Timing

`MAX_WAIT_TIME` is `60000` ms (60 seconds), set in `config.js`:

```js
// docker/config.js:6
MAX_WAIT_TIME: 60000, // 60 seconds
```

The shell script `build-setup.sh` enforces a separate outer limit of 120 s before calling `node setup.js`. The JS layer therefore operates within a window that the shell has already partially consumed. The spec notes this as a low-risk race: by the time `node setup.js` is invoked, the shell has already confirmed an HTTP-200 response at least once, so `waitForApp()` usually succeeds on its first attempt.

Sources: [docker/api.js:46](), [docker/config.js:6](), [specs/dk-flags-party-server-image.md:83]()

### Failure Mode

When 60 s elapse without an `ok` response, `waitForApp()` throws `Error('App did not become ready within the timeout period')`. The `setup()` orchestrator in `setup.js` does not catch this — it propagates up, causes `setup.js` to exit 1, and `build-setup.sh` treats that as a build failure.

---

## `auth.js` — Two-Phase Authentication

### Flow Overview

```mermaid
sequenceDiagram
    participant S as setup.js
    participant A as authenticate()
    participant I as initializeAdmin()
    participant L as login()
    participant API as FeatureHub /mr-api

    S->>A: authenticate()
    A->>I: initializeAdmin()
    I->>API: POST /mr-api/initialize
    alt status 200 or 201
        API-->>I: { accessToken | token }
        I-->>A: { success: true, token }
        A-->>S: token
    else initialization failed (already set up or error)
        API-->>I: other status
        I-->>A: { success: false, token: null }
        A->>L: login()
        L->>API: POST /mr-api/login
        alt status 200 or 201
            API-->>L: { accessToken | token }
            L-->>A: { success: true, token }
            A-->>S: token
        else login failed
            L-->>A: { success: false, token: null }
            A-->>S: null
        end
    end
```

### Phase 1: `initializeAdmin()`

`initializeAdmin()` calls `POST /mr-api/initialize` — the FeatureHub SetupService endpoint that creates the initial site admin, portfolio, and organization in a single bootstrap call. It is only callable on a fresh, uninitialized FeatureHub instance.

```js
// docker/auth.js:8-14
const response = await makeRequest('POST', '/mr-api/initialize', {
    portfolio: config.PORTFOLIO_NAME,
    organizationName: config.ORGANIZATION_NAME,
    emailAddress: config.ADMIN_EMAIL,
    password: config.ADMIN_PASSWORD,
    name: config.ADMIN_USER
});
```

Request body fields:

| Field | Config source | Default |
|-------|--------------|---------|
| `portfolio` | `PORTFOLIO_NAME` | `'integrationTest'` |
| `organizationName` | `ORGANIZATION_NAME` | `'vtex'` |
| `emailAddress` | `ADMIN_EMAIL` | `'admin@example.com'` |
| `password` | `ADMIN_PASSWORD` | `'admin'` |
| `name` | `ADMIN_USER` | `'admin'` |

Sources: [docker/auth.js:8-14](), [docker/config.js:1-10]()

### Phase 2: `login()`

When `initializeAdmin()` does not return a 200/201 — indicating the system was already bootstrapped on a previous build — `authenticate()` falls back to `login()`:

```js
// docker/auth.js:36-39
const response = await makeRequest('POST', '/mr-api/login', {
    email: config.ADMIN_EMAIL,
    password: config.ADMIN_PASSWORD
});
```

Sources: [docker/auth.js:36-39]()

### Token Extraction

Both `initializeAdmin()` and `login()` use the same extraction pattern:

```js
// docker/auth.js:20 (initializeAdmin) and auth.js:44 (login)
const token = response.data.accessToken || response.data.token;
```

The `||` fallback accommodates FeatureHub API versions that use either field name. If both are absent (e.g., the server returned a non-JSON body), `token` is `undefined`, which is coerced to falsy in the caller check.

Sources: [docker/auth.js:20](), [docker/auth.js:44]()

### `authenticate()` Orchestration

```js
// docker/auth.js:57-77
async function authenticate() {
    const initResult = await initializeAdmin();

    if (initResult.success && initResult.token) {
        return initResult.token;
    }

    if (!initResult.success) {
        // … log warnings …
        const loginResult = await login();
        if (loginResult.success && loginResult.token) {
            return loginResult.token;
        }
    }

    return null;
}
```

`authenticate()` returns the raw token string on success or `null` when both phases fail. It never throws. Callers check for `null` and abort the pipeline with a warning log (not a thrown error) when no token can be obtained.

Sources: [docker/auth.js:57-77](), [docker/setup.js:17-22]()

---

## Token Propagation to Downstream Calls

Once `setup.js` obtains the token from `authenticate()`, it passes it explicitly to every subsequent call:

```js
// docker/setup.js:27-61
const portfolioId = await getPortfolio(token);
const applicationId = await createApplication(portfolioId, token);
const environmentId = await getEnvironment(applicationId, token);
// …
const result = await createAllFeatures(applicationId, environmentId, flags, token);
```

There is no global token store or module-level variable. Each function in `portfolio.js` and `features.js` receives the token as its last argument and passes it to `makeRequest`, which injects it as `Authorization: Bearer <token>`. This design keeps the token flow explicit and the modules independently testable.

Sources: [docker/setup.js:27-61]()

---

## Configuration Reference

All values used by `api.js` and `auth.js` come from `docker/config.js`, which reads environment variables with hard-coded defaults:

| Constant | Env var | Default | Used by |
|----------|---------|---------|---------|
| `APP_URL` | `APP_URL` | `http://localhost:8085` | `waitForApp()`, `makeRequest()` base URL |
| `MAX_WAIT_TIME` | — (hard-coded) | `60000` ms | `waitForApp()` loop ceiling |
| `ADMIN_USER` | `ADMIN_USER` | `admin` | `initializeAdmin()` `name` field |
| `ADMIN_PASSWORD` | `ADMIN_PASSWORD` | `admin` | both auth phases |
| `ADMIN_EMAIL` | `ADMIN_EMAIL` | `admin@example.com` | both auth phases |
| `PORTFOLIO_NAME` | — | `integrationTest` | `initializeAdmin()` `portfolio` field |
| `ORGANIZATION_NAME` | — | `vtex` | `initializeAdmin()` `organizationName` field |

Sources: [docker/config.js:1-10]()

The `ADMIN_*` values are baked into the image layer at build time. Overriding them at `docker run` time has no effect on the embedded credentials because setup does not re-run on container start.

---

## Summary

`api.js` provides a single, uniform HTTP client (`makeRequest`) that handles Bearer token injection, JSON/text response detection, and error normalization, plus a polling readiness probe (`waitForApp`) that gates setup on a live server within a 60-second window. `auth.js` wraps that client with a two-phase strategy: attempt a first-time bootstrap via `POST /mr-api/initialize` (which seeds the admin, portfolio, and organization simultaneously), and fall back to `POST /mr-api/login` when the system has already been initialized. The extracted token is threaded explicitly through every downstream call — there is no implicit global state — making each module independently verifiable and the authorization path straightforward to audit.
