import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:guru_app/account/account_data_store.dart'; import 'package:guru_app/account/model/user.dart'; import 'package:guru_app/guru_app.dart'; import 'package:guru_app/property/app_property.dart'; import 'package:guru_utils/device/device_info.dart'; import 'package:guru_utils/device/device_utils.dart'; import 'package:retrofit/retrofit.dart'; import 'custom_transformer.dart'; import 'data/orders/orders_model.dart'; /// Created by Haoyi on 6/3/21 part 'modules/guru_api_extension.dart'; part 'guru_api.g.dart'; abstract class DioBuilder { Dio build(); } class GuruDioBuilder extends DioBuilder { final AccountDataStore accountDataStore; GuruDioBuilder() : accountDataStore = AccountDataStore.instance; Options toOptions(RequestOptions ro) { return Options( method: ro.method, sendTimeout: ro.sendTimeout, receiveTimeout: ro.receiveTimeout, extra: ro.extra, headers: ro.headers, responseType: ro.responseType, contentType: ro.contentType, validateStatus: ro.validateStatus, receiveDataWhenStatusError: ro.receiveDataWhenStatusError, followRedirects: ro.followRedirects, maxRedirects: ro.maxRedirects, requestEncoder: ro.requestEncoder, responseDecoder: ro.responseDecoder, listFormat: ro.listFormat); } @override Dio build() { Dio dio = Dio() ..transformer = CustomTransformer() ..options.connectTimeout = Duration(milliseconds: GuruApp.instance.appSpec.deployment.apiConnectTimeout) ..options.receiveTimeout = Duration(milliseconds: GuruApp.instance.appSpec.deployment.apiReceiveTimeout); dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options, RequestInterceptorHandler handler) async { DeviceInfo? deviceInfo = accountDataStore.currentDevice; final deviceId = await AppProperty.getInstance().getDeviceId(); deviceInfo ??= await DeviceUtils.buildDeviceInfo(deviceId: deviceId, firebasePushToken: "", uid: ""); final token = accountDataStore.saasToken; options.headers .addAll({"X-APP-ID": GuruApp.instance.details.saasAppId, "X-ACCESS-TOKEN": token ?? ''}); options.headers.addAll({"X-DEVICE-INFO": Uri.encodeFull(deviceInfo.toXDeviceInfo())}); handler.next(options); }, onResponse: (Response response, ResponseInterceptorHandler handler) { // Log.v("### onResponse ${response.data}"); response.data = response.data["data"] ?? response.data; handler.next(response); }, onError: (DioError err, ErrorInterceptorHandler handler) async { final token = accountDataStore.saasToken; final response = err.response; Log.v("### onError ${err.toString()}"); if (response != null && token != null && response.statusCode == 401) { // dio.lock(); try { Log.v("accountDataStore.refreshAuth()"); await accountDataStore.refreshAuth(); //获取新token } catch (e) { // Log.v("[NETWORK]: RefreshToken Failed."); handler.reject(err); } finally { // dio.unlock(); } final options = err.requestOptions.copyWith(); options.headers["X-ACCESS-TOKEN"] = accountDataStore.saasToken; try { final response = await dio.request(options.path, data: options.data, queryParameters: options.queryParameters, cancelToken: options.cancelToken, onReceiveProgress: options.onReceiveProgress, options: toOptions(options)); handler.resolve(response); } catch (error, stacktrace) { Log.v("re-request error:$error $stacktrace"); handler.reject(err); } } else { handler.reject(err); } })); dio.interceptors .add(LogInterceptor(requestBody: true, responseBody: true, logPrint: platformLogPrint)); return dio; } } @RestApi() abstract class GuruApiMethods { factory GuruApiMethods(Dio dio, {String baseUrl}) = _GuruApiMethods; static GuruApiMethods create(GuruDioBuilder dioBuilder, String baseUrl) { return GuruApiMethods(dioBuilder.build(), baseUrl: baseUrl); } // 上报 Device, 初次或者 token 更新后 @POST("/device/api/v1/devices") Future reportDevice(@Body() DeviceInfo body); // Auth @POST("/auth/api/v1/tokens/provider/secret") Future signInWithAnonymous(@Body() AnonymousLoginReqBody body); @POST("/auth/api/v1/renewals/token") Future refreshSaasToken(); @POST("/auth/api/v1/renewals/firebase") Future renewFirebaseToken(); @POST("/order/api/v1/orders/ios") Future iOSOrdersReport(@Body() OrdersReport body); @POST("/order/api/v1/orders/android") Future androidOrdersReport(@Body() OrdersReport body); } class GuruApi { static const String _saasApiDevHost = "https://dev.saas.castbox.fm"; static const String _saasApiReleaseHost = "https://saas.castbox.fm"; static bool useReleaseApi = kReleaseMode; static final GuruApi _releaseApi = GuruApi._(GuruApiMethods.create(GuruDioBuilder(), _saasApiReleaseHost)); static final GuruApi _debugApi = GuruApi._(GuruApiMethods.create(GuruDioBuilder(), _saasApiDevHost)); final GuruApiMethods _methods; static GuruApi get instance => useReleaseApi ? _releaseApi : _debugApi; static String get saasApiHost => useReleaseApi ? _saasApiReleaseHost : _saasApiDevHost; GuruApiMethods get methods => _methods; GuruApi._(this._methods); }