Skip to content

refactor(http): migrate axios → ofetch in apps/core #2760

Description

@Innei

Motivation

The v13.11.6 → v13.11.7 hotfix cycle exposed how thin the project's axios usage is. The sandbox shipped a fake axios that was just a fetch wrapper silently dropping the params config — moving the built-ins to native fetch was a strict simplification, and the same logic applies to the wider apps/core codebase: we have one HttpService Nest wrapper plus eight call sites, all using a small slice of axios.

ofetch (unjs/ofetch) is the natural replacement: native fetch semantics, built-in retry, responseType parsing, FetchError with error.status / error.data, undici Agent for the dev self-signed cert escape hatch.

Scope

  • In: apps/core/src/**
  • Out: packages/api-client/adaptors/axios.ts and its tests — that adapter is a public api-client opt-in for downstream consumers, not internal use.

Surface decision

Keep the HttpService NestJS provider as the seam, swap its internals.

  • Rename axiosReffetch returning an ofetch $Fetch instance.
  • Eight call sites change from httpService.axiosRef.get(url, opts) to httpService.fetch(url, opts) (or .fetch(url, { method: 'POST', body })).
  • Test mocks stay shape-compatible: { fetch: vi.fn() } replaces { axiosRef: { get } }.
  • Drop extend() and getAndCacheRequest() — no callers in the tree.
  • Drop the bindDebugVerboseInterceptor path — __debugLogger is never set anywhere, dead code.

Files affected

Production code (apps/core/src):

  • processors/helper/helper.http.service.ts — rewrite as ofetch wrapper
  • app.config.ts + app.config.test.tsAXIOS_CONFIG becomes the ofetch defaults (timeout; dev TLS via undici Agent)
  • processors/helper/helper.bark.service.ts:40 — POST JSON
  • processors/helper/helper.image.service.ts:119 — GET arrayBuffer
  • modules/webhook/webhook.service.ts:187 — POST JSON
  • modules/cron-task/cron-business.service.ts:118,155 — POST JSON
  • modules/update/update-download.service.ts:85,189 — GET (retry path) + GET (download with progress)
  • modules/comment/comment-country.service.ts:85 — GET JSON
  • modules/link/link-avatar.service.ts:107 — GET arrayBuffer, no-redirect

Tests:

  • test/src/modules/update/update.service.spec.ts:143,168,203 — replace axiosRef.get mocks
  • test/src/modules/comment/comment-country.spec.ts:31,139 — replace axiosRef.get mocks

package.json:

  • Remove axios, axios-retry
  • Add ofetch

Behaviour-preserving notes

  • Retry: axios-retry with exponentialDelay + 5 retries → ofetch retry: 5 with retryDelay: (ctx) => 1000 * 2 ** attempt (capped at 10s). Keep the warn log on retry.
  • Dev self-signed TLS: replace https.Agent({ rejectUnauthorized: false }) with new Agent({ connect: { rejectUnauthorized: false } }) from undici, passed as dispatcher.
  • Download progress (update-download.service.ts): preserve the admin progress UX by reading responseType: 'stream' (ReadableStream<Uint8Array>), tracking accumulated bytes vs Content-Length, and calling the existing pushProgress callback. ~30 LOC.
  • GitHub 403 detection (update-download.service.ts:98): axios.isAxiosError(e) && e.response?.status === 403e instanceof FetchError && e.status === 403.
  • No-redirect (link-avatar.service.ts): maxRedirects: 0redirect: 'manual' (native fetch option).
  • Default UA header (MX-Space/${PKG.version}) and 10s timeout: set in the shared ofetch.create({...}) defaults.

Out of scope

  • The sandbox runtime — already migrated off the fake axios stub in v13.11.7.
  • The api-client/adaptors/axios.ts public adapter — stays.
  • Any behaviour change beyond what is strictly necessary to swap the HTTP client.

Risk

Single-version refactor on master. Touches the egress path used by: friend-link avatar internalisation, webhook delivery, cron-driven HTTP probes, image proxy, IP geolocation, update download, Bark push. Worth landing as its own minor (v13.12.0) with manual smoke of at least: a webhook fire, an update check, and a friend-link avatar fetch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions