Flutter · colveor

Networking

One shared Dio client, created at bootstrap, handles headers, authentication, DPoP and errors for every request.

The client

VoBootstrap.initialize builds a Dio client with base URL apiBaseUrl/apiVersion, the configured timeouts and JSON content headers. Reach it through AuthController.dio, or make calls through a service (below).

Interceptor chain

  1. Headers — tracing, locale, user agent, tenant headers.
  2. Logging — requests, responses and errors through VoLogger.
  3. beforeAuth hooks.
  4. Auth — Bearer token, proactive and reactive refresh.
  5. DPoP — a fresh proof per request (when enabled).
  6. afterAuth hooks.

Request headers

NameDescription
X-Request-IdA new UUID v4 per call, also sent as X-Correlation-Id and X-Client-Trace-Id.
Accept-LanguageThe device locale.
User-AgentapiUserAgent, or appName/(clientVersion ?? apiVersion) (Flutter; platform; environment).
X-API-VersionapiVersion.
X-EnvironmentThe current Environment name.
X-Tenant-Id / X-Org-IdSent only when tenantId / orgId are non-empty.
Idempotency-KeyTaken from the request's extra['idempotencyKey'] when set.

Token refresh

The access token lives in memory only. Public endpoints (login, register, refresh, OTP, password reset, accept-invite) are sent without a token. For everything else:

  • Proactive: if there is no token or its JWT exp has passed, the client refreshes before sending.
  • Reactive: a 401 triggers one refresh; requests arriving meanwhile are queued, then all are retried with the new token and a new DPoP proof.
  • If the refresh fails, requests fail with UnauthorizedException and the stored session is cleared.

Services & results

Services extend VoService and wrap calls with execute, which returns a VoResult — either success(data) or failure(VoError) — instead of throwing.

Dart
import 'package:colveor/src/core/network/vo_service.dart';

class OrdersService extends VoService {
  VoResponse<List<dynamic>> list() => execute(
    () async => (await dio.get<List<dynamic>>('/orders')).data!,
  );
}

final result = await OrdersService().list();
result.when(
  success: (orders) => print(orders.length),
  failure: (error) => print(error.message),
);

Not exported yet

VoService, VoResult and VoError are not exported from a public entry file in the current release, so the import above uses the src path.

Errors

Server errors are expected as { statusCode, message, error, requestId? } and become a NetworkException, which has helpers such as isNotFound, isConflict, isValidationError, isRateLimited, isServerError and isConnectivityError. Timeouts and connection failures read "No internet connection or request timed out." Inside execute, all of these surface as a VoError with a message.