بناء من الصفر على باك إند NestJS، بقرار المالك 2026-08-04 الذي يعكس قرارَي نقل GetX (2026-07-21) واعتماد باك إند PHP (2026-07-27). لا يُورَّث سطر دارت من apps/*-archive-cubit، وسيرو مرجع بصري وسلوكي لا مصدر نسخ. الوثائق: - docs/37: الخطة الكاملة بستّ مراحل وبواباتها - docs/38: عقد الـAPI من 37 controller، معظمه متحقَّق حيّاً من السيرفر المنشور - docs/39: جرد 202 شاشة في تطبيقَي سيرو، مصنّفة داخل/خارج النطاق الطبقة الأصلية منقولة من *-archive-cubit وحدها لأنها هوية النشر: - rider_new → com.mobileapp.store.ride · shorebird 496cb3ac - driver_new → com.sefer_driver (أندرويد) · com.sefer.driver (iOS) · 68cc9345 - أُصلح تعارض: هدف RunnerTests في driver_new كان يحمل bundle الراكب الأصول مصدرها *-archive-cubit لا سيرو: أصول الأرشيف مجموعة أشمل (كل صور سيرو + صور تريبز) وخطوطها هي خطوط docs/26. استُكمل السائق بعشرة ملفات ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان). lib/ مكتوب من الصفر (11 ملف لكل تطبيق): - AppConfig بأعلام const — أساس نموذج lite/pro/max - TokenStore على التخزين الآمن، يقرأ exp محليّاً بلا حزمة خارجية - AuthInterceptor بتجديد استباقي وطلقة واحدة — عمر التوكن 15 دقيقة فقط - ApiClient و ApiException يفهم مصفوفة message في NestJS flutter analyze نظيف في التطبيقين. البناء الفعلي لم يُجرَّب بعد: بوابة المرحلة 1 تتطلّب البناء على السيرفر، والوصول إليه غير متاح حالياً. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
5.4 KiB
Swift
140 lines
5.4 KiB
Swift
|
|
import UIKit
|
|
import Flutter
|
|
import FirebaseCore
|
|
@main
|
|
@objc class AppDelegate: FlutterAppDelegate {
|
|
|
|
override func application(
|
|
_ application: UIApplication,
|
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
) -> Bool {
|
|
|
|
FirebaseApp.configure()
|
|
GeneratedPluginRegistrant.register(with: self)
|
|
|
|
// Perform security checks *before* showing the main UI
|
|
if SecurityChecks.isDeviceCompromised() { // Call Objective-C method
|
|
showSecurityAlert()
|
|
return false // Prevent app from launching further
|
|
}
|
|
//setup method channel
|
|
setupMethodChannel()
|
|
|
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
|
}
|
|
|
|
func setupMethodChannel(){
|
|
guard let controller = window?.rootViewController as? FlutterViewController else {
|
|
return
|
|
}
|
|
|
|
let channel = FlutterMethodChannel(name: "com.mobileapp.store.ride/security",
|
|
binaryMessenger: controller.binaryMessenger)
|
|
channel.setMethodCallHandler { call, result in
|
|
switch call.method {
|
|
case "isNativeRooted":
|
|
// We already check this in isDeviceCompromised,
|
|
// so we can just return the result directly.
|
|
result(SecurityChecks.isDeviceCompromised())
|
|
default:
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func showSecurityAlert() {
|
|
guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else {
|
|
// Handle the case where rootVC is nil (extremely unlikely, but good practice)
|
|
exit(0)
|
|
// return
|
|
}
|
|
|
|
let alert = UIAlertController(
|
|
title: "Security Warning".localized, // Use localized strings!
|
|
message: "Compromised device detected. Exiting.".localized,
|
|
preferredStyle: .alert
|
|
)
|
|
|
|
// Add an "OK" button with an obfuscated action
|
|
alert.addAction(UIAlertAction(title: "OK".localized, style: .destructive) { _ in
|
|
// More robust exit (see explanation below)
|
|
self.obfuscatedExit()
|
|
})
|
|
|
|
// Use a presentationDetents to prevent dismissing
|
|
if #available(iOS 15.0, *) {
|
|
alert.popoverPresentationController?.sourceView = rootVC.view
|
|
alert.popoverPresentationController?.sourceRect = CGRect(x: rootVC.view.bounds.midX, y: rootVC.view.bounds.midY, width: 0, height: 0)
|
|
alert.popoverPresentationController?.permittedArrowDirections = []
|
|
alert.presentationController?.delegate = self
|
|
if let presentationController = alert.presentationController as? UISheetPresentationController {
|
|
presentationController.detents = [.medium()] // Set a fixed height if desired
|
|
presentationController.preferredCornerRadius = 20;
|
|
presentationController.prefersEdgeAttachedInCompactHeight = true;// Set corner radius
|
|
presentationController.prefersGrabberVisible = false // Optional: show a grabber
|
|
}
|
|
|
|
} else {
|
|
// Fallback on earlier versions
|
|
}
|
|
|
|
rootVC.present(alert, animated: true, completion: nil)
|
|
}
|
|
|
|
// Obfuscated Exit (Example - you'll need a more robust solution)
|
|
func obfuscatedExit() {
|
|
// This is a *very* basic example. A real obfuscation would be MUCH more complex.
|
|
let selector = NSSelectorFromString(String(format: "%@%@", "ex", "it:"))
|
|
if responds(to: selector) {
|
|
perform(selector, with: 0) // Call exit(0) indirectly
|
|
}
|
|
}
|
|
|
|
// MARK: - Secure API Key Storage (Example)
|
|
func getAPIKey() -> String {
|
|
// DO NOT store API keys directly in your code!
|
|
// Load it from a more secure location, such as:
|
|
// 1. A configuration file that is *not* checked into source control.
|
|
// 2. Keychain Services (recommended for iOS).
|
|
// 3. A backend server (best option).
|
|
|
|
// This is just a placeholder. Replace with your actual key loading.
|
|
// In a real app, you'd fetch this from a secure source.
|
|
guard let filePath = Bundle.main.path(forResource: "keys", ofType: "plist"),
|
|
let plist = NSDictionary(contentsOfFile: filePath),
|
|
let apiKey = plist["google_maps_api_key"] as? String else {
|
|
fatalError("Couldn't find keys or API Key") // Ensure a crash if not found
|
|
}
|
|
|
|
return apiKey
|
|
}
|
|
func loadConfig() -> [String: Any]? {
|
|
guard let path = Bundle.main.path(forResource: "Config", ofType: "plist"),
|
|
let config = NSDictionary(contentsOfFile: path) as? [String: Any] else {
|
|
return nil // Or handle the error appropriately
|
|
}
|
|
return config
|
|
}
|
|
|
|
|
|
}
|
|
// Add for prevent dismissing
|
|
extension AppDelegate: UIAdaptivePresentationControllerDelegate {
|
|
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
|
return false
|
|
}
|
|
|
|
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
|
|
|
|
}
|
|
}
|
|
|
|
// Add String extension
|
|
extension String {
|
|
var localized: String {
|
|
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
|
}
|
|
}
|