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
- Headers — tracing, locale, user agent, tenant headers.
- Logging — requests, responses and errors through VoLogger.
- beforeAuth hooks.
- Auth — Bearer token, proactive and reactive refresh.
- DPoP — a fresh proof per request (when enabled).
- afterAuth hooks.
Request headers
| Name | Description |
|---|---|
X-Request-Id | A new UUID v4 per call, also sent as X-Correlation-Id and X-Client-Trace-Id. |
Accept-Language | The device locale. |
User-Agent | apiUserAgent, or appName/(clientVersion ?? apiVersion) (Flutter; platform; environment). |
X-API-Version | apiVersion. |
X-Environment | The current Environment name. |
X-Tenant-Id / X-Org-Id | Sent only when tenantId / orgId are non-empty. |
Idempotency-Key | Taken 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
exphas 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
UnauthorizedExceptionand 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.
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.