toeverything/AFFiNE · error · UserFriendlyError

NETWORK_ERROR

NETWORK_ERROR

Error message

Network error: ${err.message}

What it means

HttpConnection.fetch wraps globalThis.fetch; any fetch rejection (DNS failure, connection refused, CORS, TLS error, or the built-in 15s default timeout aborting with 'request timeout') is converted into a UserFriendlyError with status 504 and code/type/name NETWORK_ERROR. The original message (e.g. 'Failed to fetch' or 'request timeout') and stack are preserved inside.

Source

Thrown at packages/common/nbstore/src/impls/cloud/http.ts:40

    const timeoutId =
      timeout > 0
        ? setTimeout(() => {
            abortController.abort(new Error('request timeout'));
          }, timeout)
        : undefined;

    const res = await globalThis
      .fetch(new URL(input, this.serverBaseUrl), {
        ...init,
        signal: abortController.signal,
        headers: {
          ...this.requestHeaders,
          ...init?.headers,
          'x-affine-version': BUILD_CONFIG.appVersion,
        },
      })
      .catch(err => {
        throw new UserFriendlyError({
          status: 504,
          code: 'NETWORK_ERROR',
          type: 'NETWORK_ERROR',
          name: 'NETWORK_ERROR',
          message: `Network error: ${err.message}`,
          stacktrace: err.stack,
        });
      });
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    if (!res.ok && res.status !== 404) {
      if (res.status === 413) {
        throw new UserFriendlyError({
          status: 413,
          code: 'CONTENT_TOO_LARGE',
          type: 'CONTENT_TOO_LARGE',
          name: 'CONTENT_TOO_LARGE',

View on GitHub (pinned to b4c8548c09)

Solutions

  1. If it's a timeout ('Network error: request timeout'), pass a larger timeout in init (connection.fetch(url, { timeout: 60000 })) or 0 to disable — note blob uploads already set no timeout.
  2. Verify serverBaseUrl is correct and the server is reachable (curl the URL from the same environment).
  3. For offline/CORS: check network connectivity and the server's CORS configuration for the client origin.
  4. Handle it in UI: err instanceof UserFriendlyError && err.code === 'NETWORK_ERROR' → offer retry; keep the original message for diagnostics.

Example fix

// before
const res = await connection.fetch('/api/workspaces/' + id + '/blobs/' + key);
// >15s downloads/reads throw Network error: request timeout

// after
const res = await connection.fetch(
  '/api/workspaces/' + id + '/blobs/' + key,
  { timeout: 60_000 }
);
Defensive patterns

Strategy: retry

Validate before calling

async function reachable(baseUrl: string): Promise<boolean> {
  try { await fetch(new URL('/api/workspaces', baseUrl), { method: 'HEAD' }); return true; } catch { return false; }
}

Type guard

import { UserFriendlyError } from '@affine/error';
const isNetworkError = (e: unknown): e is UserFriendlyError =>
  e instanceof UserFriendlyError && e.code === 'NETWORK_ERROR';

Try / catch

async function fetchWithRetry(url: string, init?: RequestInit & { timeout?: number }, tries = 3) {
  for (let i = 0; ; i++) {
    try { return await connection.fetch(url, init); }
    catch (e) {
      if (i < tries - 1 && e instanceof UserFriendlyError && e.code === 'NETWORK_ERROR') {
        await new Promise(r => setTimeout(r, 2 ** i * 500)); continue;
      }
      throw e;
    }
  }
}

Prevention

When it happens

Trigger: Calling connection.fetch/gql while offline; wrong/unreachable serverBaseUrl; expired/self-signed certificates; a request exceeding init.timeout (default 15000ms — the abort surfaces as 'Network error: request timeout'); CORS blocking in the browser.

Common situations: Client lost connectivity mid-session; dev pointing at a localhost server that is not running; slow blob uploads hitting the 15s default because the timeout option wasn't raised; VPN/DNS issues; deploying the web app on a domain not allowed by server CORS.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/b49ad8394e07d76a. Report an issue: GitHub.