feat(flutter/api): dio client with Bearer interceptor + ApiError
ApiError.fromDio handles both server envelope shapes: {"error":"code"}
(admin endpoints) and {"error":{"code","message"}} (most others). Same
dual-shape problem the web apiFetch already solves. Token comes from a
resolver function so the auth provider can swap implementations
without touching the client.
ErrorCopy is a lazy singleton over assets/error-copy.json with the
same fallback chain web uses (specific code -> unknown -> hard-coded).
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
typedef TokenResolver = Future<String?> Function();
|
||||
|
||||
class ApiClient {
|
||||
/// Builds a dio instance pinned to [baseUrl] with a Bearer-auth
|
||||
/// interceptor that pulls the current token from [tokenResolver]
|
||||
/// on every request. Server already supports cookie OR bearer
|
||||
/// (internal/auth/session.go); we use bearer to skip cookie-jar.
|
||||
static Dio buildDio({
|
||||
required String baseUrl,
|
||||
required TokenResolver tokenResolver,
|
||||
}) {
|
||||
final d = Dio(BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 8),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
contentType: Headers.jsonContentType,
|
||||
responseType: ResponseType.json,
|
||||
));
|
||||
d.interceptors.add(InterceptorsWrapper(onRequest: (opts, handler) async {
|
||||
final t = await tokenResolver();
|
||||
if (t != null && t.isNotEmpty) {
|
||||
opts.headers['Authorization'] = 'Bearer $t';
|
||||
}
|
||||
handler.next(opts);
|
||||
}));
|
||||
return d;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user