Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixUI/hesabix_ui/lib/config/app_config.dart
Babak Alizadeh 88ef9a6b9b fix: harden quick customer resolve auth and candidate loading
Require can_access_business for customer body/query endpoints, prefilter
person candidates in SQL instead of loading the whole business, and limit
local web API port rewriting to loopback/LAN hosts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 19:21:54 +00:00

108 lines
3.8 KiB
Dart
Executable file

import 'package:flutter/foundation.dart' show kIsWeb;
class AppConfig {
AppConfig._();
static const String _envApiBaseUrl = String.fromEnvironment(
'API_BASE_URL',
defaultValue: '',
);
/// API Base URL
///
/// - اگر با `--dart-define=API_BASE_URL=...` مقداردهی شود همان استفاده می‌شود.
/// - در وب اگر مقداردهی نشده باشد، از همان origin فعلی استفاده می‌کنیم (به شرط اینکه
/// روی reverse proxy مسیرهای `/api/*` و `/ws/*` به بک‌اند پاس داده شوند).
/// - در غیر وب، پیش‌فرض `http://localhost:8000` است.
static String get apiBaseUrl {
return resolveApiBaseUrl(
configuredValue: _envApiBaseUrl,
isWebBuild: kIsWeb,
currentUri: Uri.base,
);
}
static bool isLocalWebPreviewHost(String host) {
final normalized = host.toLowerCase();
if (normalized == 'localhost' ||
normalized == '127.0.0.1' ||
normalized == '::1') {
return true;
}
// RFC1918 — پیش‌نمایش روی LAN (مثلاً 192.168.x.x:8080)
if (RegExp(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$').hasMatch(normalized)) {
return true;
}
if (RegExp(r'^192\.168\.\d{1,3}\.\d{1,3}$').hasMatch(normalized)) {
return true;
}
final match = RegExp(r'^172\.(\d{1,3})\.\d{1,3}\.\d{1,3}$').firstMatch(
normalized,
);
if (match != null) {
final second = int.tryParse(match.group(1)!);
if (second != null && second >= 16 && second <= 31) return true;
}
return false;
}
static String resolveApiBaseUrl({
required String configuredValue,
required bool isWebBuild,
required Uri currentUri,
}) {
final value = configuredValue.trim();
if (value.isNotEmpty) return value;
if (isWebBuild) {
final host = currentUri.host.toLowerCase();
final isLoopback =
host == 'localhost' || host == '127.0.0.1' || host == '::1';
// فقط پیش‌نمایش Flutter روی LAN/لوپ‌بک؛ hostname عمومی روی 8080 را
// به‌اشتباه به پورت API نفرست.
final isLocalPreview =
isLoopback ||
(currentUri.port == 8080 && isLocalWebPreviewHost(host));
if (isLocalPreview) {
// Flutter web-server روی 8080 فقط فایل استاتیک سرو می‌کند. فرستادن
// درخواست‌های API به همان origin خطا می‌دهد. این تشخیص شامل IP شبکه
// نیز هست تا پیش‌نمایش روی دستگاه دیگری API همان میزبان را ببیند.
return Uri(
scheme: currentUri.scheme,
host: currentUri.host,
port: 8000,
).origin;
}
// مثال: http://localhost:8080 یا https://arc.hesabix.ir
// در این حالت انتظار داریم reverse proxy مسیرهای api/ws را route کند.
return currentUri.origin;
}
return 'http://localhost:8000';
}
static const String _envAppPublicUrl = String.fromEnvironment(
'APP_PUBLIC_URL',
defaultValue: '',
);
/// آدرس پایهٔ وب‌اپ (بدون /login) — برای ساخت لینک بازیابی وقتی API لینک ندهد
static String get appPublicBaseUrl {
final v = _envAppPublicUrl.trim();
if (v.isNotEmpty) {
return v.replaceAll(RegExp(r'/+$'), '');
}
if (kIsWeb) {
return Uri.base.origin;
}
return apiBaseUrl.replaceAll(RegExp(r'/+$'), '');
}
/// لینک کامل: باز کردن /login?reset_token=... (هم‌راستا با بک‌اند)
static String? buildPasswordResetUrl(String token) {
if (token.isEmpty) return null;
final b = appPublicBaseUrl;
if (b.isEmpty) return null;
return '$b/login?reset_token=${Uri.encodeComponent(token)}';
}
}