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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions