Commit 107cdd2d authored by DatHV's avatar DatHV
Browse files

update build x-app-sdk

parent 9bb8aadd
Pipeline #2082 failed with stages
in 0 seconds
# Close App Integration với x-app-sdk # Close App Integration
## Tổng quan ## Tổng quan
...@@ -154,6 +154,5 @@ http://localhost:8080/?token=test123&user={"id":"user123"} ...@@ -154,6 +154,5 @@ http://localhost:8080/?token=test123&user={"id":"user123"}
- `lib/web/web_helper_web.dart` - Implementation cho web - `lib/web/web_helper_web.dart` - Implementation cho web
- `lib/web/web_helper_stub.dart` - Stub cho non-web platforms - `lib/web/web_helper_stub.dart` - Stub cho non-web platforms
- `lib/web/x_app_sdk_service.dart` - Service gọi JavaScript
- `web/index.html` - JavaScript implementation - `web/index.html` - JavaScript implementation
- `lib/web/close_app_example.dart` - Ví dụ sử dụng - `lib/web/close_app_example.dart` - Ví dụ sử dụng
...@@ -34,10 +34,6 @@ ...@@ -34,10 +34,6 @@
./scripts/open_browser_cors_disabled.sh ./scripts/open_browser_cors_disabled.sh
``` ```
### **Test x-app-sdk:**
```bash
./scripts/test_x_app_sdk.sh
```
## 📁 **Cấu trúc đơn giản:** ## 📁 **Cấu trúc đơn giản:**
...@@ -53,12 +49,12 @@ flutter_app_mypoint/ ...@@ -53,12 +49,12 @@ flutter_app_mypoint/
│ ├── switch_env.sh # Chuyển đổi môi trường │ ├── switch_env.sh # Chuyển đổi môi trường
│ ├── run_web_complete.sh # Chạy web với CORS │ ├── run_web_complete.sh # Chạy web với CORS
│ ├── open_browser_cors_disabled.sh # Mở Chrome với CORS disabled │ ├── open_browser_cors_disabled.sh # Mở Chrome với CORS disabled
└── test_x_app_sdk.sh # Test x-app-sdk └── test_web.sh # Test web
└── lib/web/ # x-app-sdk integration └── lib/web/ # Web integration
``` ```
## ✅ **Kết quả:** ## ✅ **Kết quả:**
-**Đơn giản**: Chỉ 2 lệnh chính -**Đơn giản**: Chỉ 2 lệnh chính
-**Dễ nhớ**: `./run_dev.sh``./run_prod.sh` -**Dễ nhớ**: `./run_dev.sh``./run_prod.sh`
-**x-app-sdk**: Tích hợp đầy đủ -**Web**: Tích hợp đầy đủ
-**Sẵn sàng**: Chạy ngay -**Sẵn sàng**: Chạy ngay
...@@ -18,12 +18,6 @@ ...@@ -18,12 +18,6 @@
- `./scripts/run_web_complete.sh` - Chạy development với CORS - `./scripts/run_web_complete.sh` - Chạy development với CORS
- `./scripts/export_and_run.sh` - Export + chạy + mở browser - `./scripts/export_and_run.sh` - Export + chạy + mở browser
#### **3. Tích hợp x-app-sdk:**
- `lib/web/x_app_sdk_service.dart` - Service chính
- `lib/web/web_helper_web.dart` - Web helper functions
- `lib/web/web_helper_stub.dart` - Stub cho non-web
- `web/index.html` - JavaScript integration
- `X_APP_SDK_INTEGRATION.md` - Hướng dẫn tích hợp
## 🚀 **Cách sử dụng đơn giản:** ## 🚀 **Cách sử dụng đơn giản:**
...@@ -79,22 +73,6 @@ from origin 'http://localhost:8080' has been blocked by CORS policy ...@@ -79,22 +73,6 @@ from origin 'http://localhost:8080' has been blocked by CORS policy
- Cài "CORS Unblock" extension - Cài "CORS Unblock" extension
- Enable khi test - Enable khi test
## 🔧 **x-app-sdk Integration:**
### **Chức năng:**
- Lấy token từ Super App: `window.getToken()`
- Lấy user info từ Super App: `window.getInfo('USER_ID')`
- Retry mechanism: Tối đa 3 lần
- Fallback: URL parameters và localStorage
### **Test:**
```bash
# Test với URL parameters
http://localhost:8080?token=abc123&userId=user456
# Test x-app-sdk
./scripts/test_x_app_sdk.sh
```
## 📁 **Cấu trúc đơn giản:** ## 📁 **Cấu trúc đơn giản:**
...@@ -103,20 +81,18 @@ flutter_app_mypoint/ ...@@ -103,20 +81,18 @@ flutter_app_mypoint/
├── assets/config/ ├── assets/config/
│ └── env.json # Config duy nhất │ └── env.json # Config duy nhất
├── lib/web/ ├── lib/web/
│ ├── x_app_sdk_service.dart # x-app-sdk service │ └── (web helper files)
│ ├── web_helper_web.dart # Web functions
│ └── web_helper_stub.dart # Stub functions
├── web/ ├── web/
│ └── index.html # JavaScript integration │ └── index.html # JavaScript integration
├── export_web.sh # Export script ├── export_web.sh # Export script
└── scripts/ └── scripts/
├── run_web_complete.sh # Development ├── run_web_complete.sh # Development
├── export_and_run.sh # Export + run ├── export_and_run.sh # Export + run
└── test_x_app_sdk.sh # Test x-app-sdk └── test_web.sh # Test web
``` ```
## 🎯 **Kết quả:** ## 🎯 **Kết quả:**
-**Đơn giản**: Chỉ 1 config file -**Đơn giản**: Chỉ 1 config file
-**Dễ hiểu**: Không còn phức tạp -**Dễ hiểu**: Không còn phức tạp
-**x-app-sdk**: Tích hợp đầy đủ -**Web**: Tích hợp đầy đủ
-**Sẵn sàng**: Export và deploy ngay -**Sẵn sàng**: Export và deploy ngay
# X-App-SDK Integration Guide for Mini App # X-App-SDK Integration Guide
## Tổng quan ## Tổng quan
Tài liệu này mô tả cách tích hợp mini app với `x-app-sdk` để lấy thông tin token, user từ Super App. Super App đã có sẵn `x-app-sdk`, mini app chỉ cần gọi các method để lấy dữ liệu. Tài liệu này mô tả cách tích hợp mini app với `x-app-sdk` để lấy token và đóng app từ Super App. Implementation này đơn giản và chỉ sử dụng 2 API chính: `getToken()``closeApp()`.
## Cài đặt ## Cài đặt
### 1. Build Flutter web app ### 1. Install x-app-sdk
```bash
npm install x-app-sdk@^1.1.2
```
### 2. Build Flutter web app
```bash ```bash
flutter build web flutter build web
...@@ -16,63 +22,37 @@ flutter build web ...@@ -16,63 +22,37 @@ flutter build web
### Trong Super App ### Trong Super App
Super App đã có sẵn `x-app-sdk` và cung cấp các method global: Super App không cần làm gì đặc biệt. x-app-sdk sẽ tự động detect Super App environment và sử dụng các method có sẵn.
```javascript **Lưu ý**: Mini app sử dụng x-app-sdk thật từ npm package, không phải mock.
// Super App đã có sẵn các method này:
// window.getToken() - Lấy token đăng nhập
// window.getInfo(key) - Lấy thông tin người dùng theo key
// Mini app sẽ tự động gọi:
// window.getToken() - Lấy token
// window.getInfo('USER_ID') - Lấy ID người dùng
// window.getInfo('USER_NAME') - Lấy tên người dùng
// window.getInfo('USER_EMAIL') - Lấy email người dùng
// window.getInfo('USER_PHONE') - Lấy số điện thoại
```
### Trong Mini App (Flutter) ### Trong Mini App (Flutter)
Mini app sẽ tự động lấy dữ liệu từ Super App: Mini app sẽ tự động lấy token từ Super App khi khởi động:
```dart ```dart
// Lấy token // Token được lấy tự động trong splash screen
String? token = webGetAppHostToken(); // Không cần gọi thủ công
// Lấy user info // Đóng app và trả về Super App
Map<String, dynamic>? user = webGetAppHostUser(); webCloseApp({
'message': 'Task completed',
// Kiểm tra data có sẵn 'timestamp': DateTime.now().millisecondsSinceEpoch,
bool isReady = webIsAppHostDataReady(); });
// Kiểm tra x-app-sdk có sẵn từ Super App
bool sdkAvailable = webIsSDKAvailable();
// Lấy error message nếu có
String? error = webGetAppHostError();
// Lấy thông tin user theo key cụ thể
dynamic userInfo = await webGetUserInfoByKey('USER_NAME');
// Lấy token bất đồng bộ
String? token = await webGetTokenAsync();
``` ```
## API Reference ## API Reference
### Web Helper Functions ### Web Helper Functions
- `webGetAppHostToken()`: Lấy token từ Super App
- `webGetAppHostUser()`: Lấy thông tin user từ Super App
- `webIsAppHostDataReady()`: Kiểm tra data có sẵn
- `webIsSDKAvailable()`: Kiểm tra x-app-sdk có sẵn từ Super App
- `webGetAppHostError()`: Lấy error message
- `webInitializeXAppSDK()`: Khởi tạo x-app-sdk service - `webInitializeXAppSDK()`: Khởi tạo x-app-sdk service
- `webStoreAppHostData(token, user)`: Lưu data vào localStorage - `webGetToken()`: Lấy token từ Super App
- `webClearAppHostData()`: Xóa data - `webCloseApp(data)`: Đóng app và trả về Super App với data
- `webCallXAppSDKMethod(methodName, args)`: Gọi method của x-app-sdk - `webIsSDKInitialized()`: Kiểm tra SDK đã khởi tạo chưa
- `webGetUserInfoByKey(key)`: Lấy thông tin user theo key - `webGetCachedToken()`: Lấy token đã cache
- `webGetTokenAsync()`: Lấy token bất đồng bộ - `webGetLastError()`: Lấy error message cuối cùng
- `webClearTokenCache()`: Xóa token cache
- `webResetSDK()`: Reset SDK service
### XAppSDKService ### XAppSDKService
...@@ -82,105 +62,67 @@ final service = XAppSDKService(); ...@@ -82,105 +62,67 @@ final service = XAppSDKService();
// Khởi tạo // Khởi tạo
await service.initialize(); await service.initialize();
// Lấy dữ liệu // Lấy token
String? token = service.token; String? token = await service.getToken();
Map<String, dynamic>? user = service.user;
bool isReady = service.isReady;
String? error = service.error;
// Lưu dữ liệu
service.storeData(token, user);
// Xóa dữ liệu
service.clearData();
// Gọi method SDK trực tiếp
dynamic result = await service.callSDKMethod('getInfo', ['USER_NAME']);
// Lấy thông tin user theo key
dynamic userInfo = await service.getUserInfo('USER_EMAIL');
// Lấy token bất đồng bộ // Đóng app
String? token = await service.getTokenAsync(); await service.closeApp({'message': 'Done'});
// Kiểm tra SDK có sẵn // Kiểm tra trạng thái
bool sdkAvailable = service.isSDKAvailable(); bool isReady = service.isInitialized;
String? cachedToken = service.cachedToken;
String? error = service.lastError;
``` ```
## X-App-SDK Methods (từ Super App) ## Luồng hoạt động
### getToken() 1. **Khởi tạo**: App khởi tạo x-app-sdk khi start
Lấy token đăng nhập người dùng từ Super App. 2. **Splash Screen**: Tự động gọi `getToken()` để lấy token
3. **Fallback**: Nếu SDK không có token, fallback về URL params
```javascript 4. **Login**: Sử dụng token để đăng nhập
// Super App cung cấp method này 5. **Close App**: Khi cần đóng app, gọi `closeApp()`
window.getToken().then(token => {
console.log('Token người dùng:', token);
}).catch(error => {
console.error('Lỗi lấy token:', error);
});
```
### getInfo(key) ## Test
Lấy thông tin người dùng từ Super App theo key.
```javascript ### Local Development
// Super App cung cấp method này
// Lấy user ID
window.getInfo('USER_ID').then(info => {
console.log('ID người dùng:', info.data);
}).catch(error => {
console.error('Lỗi lấy thông tin:', error);
});
// Lấy thông tin khác ```bash
window.getInfo('USER_NAME').then(info => { # Test với mock SDK
console.log('Tên người dùng:', info.data); ./scripts/test_x_app_sdk.sh
});
``` ```
### Available Keys ### Production
- `'USER_ID'`: ID người dùng
- `'USER_NAME'`: Tên người dùng
- `'USER_EMAIL'`: Email người dùng
- `'USER_PHONE'`: Số điện thoại
## Fallback Methods
Nếu `x-app-sdk` không khả dụng, app sẽ thử các phương pháp fallback: Không cần thay đổi gì. x-app-sdk sẽ tự động detect Super App environment và hoạt động đúng.
1. **URL Parameters**: `?token=xxx&user=yyy` **Lưu ý**: x-app-sdk được load từ npm package, tự động detect Super App environment.
2. **localStorage**: `app_host_token`, `app_host_user`
## Debugging ## Troubleshooting
Để debug, mở Developer Tools và kiểm tra:
1. Console logs với prefix `🔍`, `✅`, `❌`, `⚠️` ### SDK không khởi tạo được
2. `window.AppHostData` object
3. Network tab để xem việc load x-app-sdk
## Lưu ý - Kiểm tra console log: `❌ XAppSDKService: x-app-sdk not found in window`
- Đảm bảo Super App đã load x-app-sdk trước khi load mini app
- Chỉ hoạt động trên web platform ### Token không lấy được
- Super App đã có sẵn `x-app-sdk` library
- Mini app gọi `window.getToken()``window.getInfo()` từ Super App
- Data được lưu trong localStorage để sử dụng lại
- App tự động listen cho updates từ Super App
- SDK với dữ liệu thật chỉ được trả ra khi chạy trên Super App
- Khi chạy trên web thường sẽ có data mẫu có cấu trúc tương tự data thật
## Troubleshooting - Kiểm tra console log: `❌ SplashScreen - Failed to get token from SDK`
- Fallback sẽ tự động sử dụng URL params
- Kiểm tra Super App có expose `getToken()` method không
### Lỗi thường gặp: ### CloseApp không hoạt động
1. **"x-app-sdk not available from Super App"**: Super App chưa expose methods - Kiểm tra console log: `❌ XAppSDKService: closeApp method not found`
2. **"No data available from Super App"**: Không lấy được data từ Super App - Fallback sẽ tự động sử dụng `window.history.back()` hoặc `window.close()`
3. **CORS errors**: Cần cấu hình CORS cho Super App
### Giải pháp: ## Files liên quan
1. Kiểm tra Super App đã expose `window.getToken()``window.getInfo()` chưa - `lib/web/x_app_sdk_service.dart` - Service chính
2. Kiểm tra console logs để xem chi tiết lỗi - `lib/web/web_helper_web.dart` - Web implementation
3. Sử dụng fallback methods nếu cần - `lib/web/web_helper_stub.dart` - Stub cho non-web platforms
4. Kiểm tra `webIsSDKAvailable()` để xác nhận SDK có sẵn - `lib/web/web_helper.dart` - Export file
- `lib/screen/splash/splash_screen_viewmodel.dart` - Tích hợp trong splash
- `lib/base/app_navigator.dart` - Sử dụng closeApp
- `lib/core/app_initializer.dart` - Khởi tạo SDK
- `web/index.html` - Mock SDK cho development
- `scripts/test_x_app_sdk.sh` - Script test
{ {
"flavor":"dev", "flavor":"pro",
"baseUrl":"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest", "baseUrl":"https://api.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-dev", "t3Token":"runner-env-flavor-pro",
"enableLogging":true "enableLogging":false
} }
...@@ -19,8 +19,8 @@ flutter clean ...@@ -19,8 +19,8 @@ flutter clean
rm -rf .dart_tool build rm -rf .dart_tool build
flutter pub get flutter pub get
# Install web dependencies for x-app-sdk # Install web dependencies
echo "📦 Installing web dependencies for x-app-sdk..." echo "📦 Installing web dependencies..."
cd web cd web
if [ -f "package.json" ]; then if [ -f "package.json" ]; then
npm install npm install
...@@ -130,17 +130,6 @@ Có thể override bằng query parameters: `?env=production` ...@@ -130,17 +130,6 @@ Có thể override bằng query parameters: `?env=production`
Firebase config được load từ `firebase_options.dart` Firebase config được load từ `firebase_options.dart`
Đảm bảo Firebase project được cấu hình đúng cho domain này. Đảm bảo Firebase project được cấu hình đúng cho domain này.
## x-app-sdk Integration
App được tích hợp với x-app-sdk để giao tiếp với Super App:
- **Token retrieval**: Lấy token từ Super App
- **User info**: Lấy thông tin user từ Super App
- **Fallback methods**: URL parameters và localStorage
- **Retry mechanism**: Tối đa 3 lần retry
### Test x-app-sdk:
1. Mở `test_urls.html` để test với URL parameters
2. Kiểm tra console log để debug
3. Test với Super App environment
## CORS Configuration ## CORS Configuration
App cần CORS headers để gọi API: App cần CORS headers để gọi API:
...@@ -154,7 +143,6 @@ Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, Acc ...@@ -154,7 +143,6 @@ Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, Acc
- Nếu có lỗi CORS, cấu hình server để allow origin của domain - Nếu có lỗi CORS, cấu hình server để allow origin của domain
- Nếu có lỗi Firebase, kiểm tra domain trong Firebase console - Nếu có lỗi Firebase, kiểm tra domain trong Firebase console
- Nếu có lỗi assets, đảm bảo đường dẫn relative đúng - Nếu có lỗi assets, đảm bảo đường dẫn relative đúng
- Nếu có lỗi x-app-sdk, kiểm tra Super App environment
EOF EOF
# Tạo file .htaccess cho Apache # Tạo file .htaccess cho Apache
...@@ -320,11 +308,6 @@ EOF ...@@ -320,11 +308,6 @@ EOF
echo "4. Hoặc sử dụng Node.js: npm install && npm start" echo "4. Hoặc sử dụng Node.js: npm install && npm start"
echo "" echo ""
echo "🔧 X-App-SDK Integration:" echo "🔧 X-App-SDK Integration:"
echo "- Mini app đã được tích hợp x-app-sdk để lấy token và user info từ Super App"
echo "- Super App cần expose window.getToken() và window.getInfo() methods"
echo "- Retry mechanism: Tối đa 3 lần retry cho Super App data"
echo "- Fallback methods: URL parameters và localStorage"
echo "- Xem chi tiết trong X_APP_SDK_INTEGRATION.md"
echo "" echo ""
echo "🌐 Test local: cd $EXPORT_DIR && python3 -m http.server 8080" echo "🌐 Test local: cd $EXPORT_DIR && python3 -m http.server 8080"
echo "💡 Tip: Nếu muốn auto-run server, chạy: (cd $EXPORT_DIR && python3 -m http.server 8080 &)" echo "💡 Tip: Nếu muốn auto-run server, chạy: (cd $EXPORT_DIR && python3 -m http.server 8080 &)"
......
...@@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; ...@@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart'; import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart';
import '../configs/constants.dart'; import '../configs/constants.dart';
import '../preference/data_preference.dart'; import '../preference/data_preference.dart';
import '../resources/base_color.dart'; import '../resources/base_color.dart';
...@@ -10,6 +9,7 @@ import '../shared/router_gage.dart'; ...@@ -10,6 +9,7 @@ import '../shared/router_gage.dart';
import '../widgets/alert/custom_alert_dialog.dart'; import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart'; import '../widgets/alert/data_alert_model.dart';
import '../widgets/alert/popup_data_model.dart'; import '../widgets/alert/popup_data_model.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart';
class AppNavigator { class AppNavigator {
static final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>(); static final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
...@@ -37,13 +37,13 @@ class AppNavigator { ...@@ -37,13 +37,13 @@ class AppNavigator {
text: "Đã hiểu", text: "Đã hiểu",
onPressed: () { onPressed: () {
_authDialogShown = false; _authDialogShown = false;
if (kIsWeb) { // if (kIsWeb) {
webCloseApp({ // webCloseApp({
'message': message.isNotEmpty ? message : description, // 'message': message.isNotEmpty ? message : description,
'timestamp': DateTime.now().millisecondsSinceEpoch, // 'timestamp': DateTime.now().millisecondsSinceEpoch,
}); // });
return; // return;
} // }
final phone = DataPreference.instance.phoneNumberUsedForLoginScreen; final phone = DataPreference.instance.phoneNumberUsedForLoginScreen;
if (phone.isNotEmpty) { if (phone.isNotEmpty) {
Get.offAllNamed(loginScreen, arguments: {'phone': phone}); Get.offAllNamed(loginScreen, arguments: {'phone': phone});
......
import 'package:flutter/foundation.dart';
class UrlParams {
static String? _token;
static String? _userId;
static String? get token => _token;
static String? get userId => _userId;
static void setToken(String? token) => _token = token;
static void setUserId(String? userId) => _userId = userId;
static Map<String, String?> get allParams => {
'token': token,
'user_id': userId,
};
static bool get hasToken => token != null && token!.isNotEmpty;
static bool get hasUserId => userId != null && userId!.isNotEmpty;
// Helper method để lấy token cho API calls
static String? getTokenForApi() {
return token;
}
// Helper method để lấy userId cho API calls
static String? getUserIdForApi() {
return userId;
}
@override
String toString() => 'UrlParams: $allParams';
}
...@@ -10,8 +10,7 @@ import 'package:mypoint_flutter_app/firebase/push_notification.dart'; ...@@ -10,8 +10,7 @@ import 'package:mypoint_flutter_app/firebase/push_notification.dart';
import 'package:mypoint_flutter_app/firebase/push_setup.dart'; import 'package:mypoint_flutter_app/firebase/push_setup.dart';
import 'package:mypoint_flutter_app/base/app_loading.dart'; import 'package:mypoint_flutter_app/base/app_loading.dart';
import 'package:mypoint_flutter_app/env_loader.dart'; import 'package:mypoint_flutter_app/env_loader.dart';
import 'package:mypoint_flutter_app/web/web_app_initializer.dart'; import 'package:mypoint_flutter_app/web/web_helper.dart';
import 'package:mypoint_flutter_app/core/deep_link_service.dart';
/// Main app initialization and setup /// Main app initialization and setup
class AppInitializer { class AppInitializer {
...@@ -30,13 +29,27 @@ class AppInitializer { ...@@ -30,13 +29,27 @@ class AppInitializer {
await _initializeFirebase(); await _initializeFirebase();
// Fetch user point if logged in // Fetch user point if logged in
await _fetchUserPointIfLoggedIn(); await _fetchUserPointIfLoggedIn();
// Initialize web-specific features // Initialize web-specific features (including x-app-sdk)
await WebAppInitializer.initialize(); await _initializeWebFeatures();
// Initialize deep links
await DeepLinkService().initialize();
print('✅ App initialization completed'); print('✅ App initialization completed');
} }
/// Initialize web-specific features
static Future<void> _initializeWebFeatures() async {
if (kIsWeb) {
print('🌐 Initializing web-specific features...');
try {
// Initialize x-app-sdk
await webInitializeXAppSDK();
print('✅ Web features initialized successfully');
} catch (e) {
print('❌ Error initializing web features: $e');
}
} else {
print('📱 Skipping web features initialization for mobile');
}
}
/// Initialize Firebase and FCM (mobile only) /// Initialize Firebase and FCM (mobile only)
static Future<void> _initializeFirebase() async { static Future<void> _initializeFirebase() async {
if (!kIsWeb) { if (!kIsWeb) {
......
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
...@@ -6,11 +7,15 @@ import 'package:mypoint_flutter_app/resources/base_color.dart'; ...@@ -6,11 +7,15 @@ import 'package:mypoint_flutter_app/resources/base_color.dart';
import 'package:mypoint_flutter_app/screen/splash/splash_screen.dart'; import 'package:mypoint_flutter_app/screen/splash/splash_screen.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'package:mypoint_flutter_app/core/app_initializer.dart'; import 'package:mypoint_flutter_app/core/app_initializer.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
if (kIsWeb) {
setUrlStrategy(PathUrlStrategy());
}
// Initialize all app features // Initialize all app features
await AppInitializer.initialize(); await AppInitializer.initialize();
// Run the app // Run the app
......
...@@ -4,7 +4,6 @@ import 'package:shared_preferences/shared_preferences.dart'; ...@@ -4,7 +4,6 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../model/auth/login_token_response_model.dart'; import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_response_model.dart'; import '../model/auth/profile_response_model.dart';
import '../screen/popup_manager/popup_manager_viewmodel.dart'; import '../screen/popup_manager/popup_manager_viewmodel.dart';
import '../web/web_helper_stub.dart';
class DataPreference { class DataPreference {
static final DataPreference _instance = DataPreference._internal(); static final DataPreference _instance = DataPreference._internal();
...@@ -78,7 +77,6 @@ class DataPreference { ...@@ -78,7 +77,6 @@ class DataPreference {
Future<void> clearLoginToken() async { Future<void> clearLoginToken() async {
_loginToken = null; _loginToken = null;
webClearStorage();
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.remove('login_token'); await prefs.remove('login_token');
await PopupManagerViewModel.instance.reset(); await PopupManagerViewModel.instance.reset();
......
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_viewmodel.dart'; import 'package:mypoint_flutter_app/networking/restful_api_viewmodel.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
import '../../base/base_response_model.dart'; import '../../base/base_response_model.dart';
import '../../configs/url_params.dart';
import '../../model/auth/login_token_response_model.dart'; import '../../model/auth/login_token_response_model.dart';
import '../../model/auth/profile_response_model.dart'; import '../../model/auth/profile_response_model.dart';
import '../../web/web_helper_stub.dart';
import 'models/update_response_model.dart'; import 'models/update_response_model.dart';
import '../../preference/data_preference.dart'; import '../../preference/data_preference.dart';
import '../../preference/point/point_manager.dart'; import '../../preference/point/point_manager.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../web/web_helper.dart';
import '../popup_manager/popup_manager_viewmodel.dart'; import '../popup_manager/popup_manager_viewmodel.dart';
class SplashScreenViewModel extends RestfulApiViewModel { class SplashScreenViewModel extends RestfulApiViewModel {
...@@ -43,19 +43,58 @@ class SplashScreenViewModel extends RestfulApiViewModel { ...@@ -43,19 +43,58 @@ class SplashScreenViewModel extends RestfulApiViewModel {
_freshUserProfile(); _freshUserProfile();
return; return;
} }
final tokenFormWeb = UrlParams.getTokenForApi() ?? "";
print('🔍 SplashScreen - Token from URL: $tokenFormWeb'); // Try to get token from x-app-sdk first (for web)
if (tokenFormWeb.isEmpty) { String? token = await _getTokenFromSDK();
if (token.orEmpty.isEmpty) {
_directionWhenTokenInvalid(); _directionWhenTokenInvalid();
return; return;
} }
print('✅ Token found, proceeding with login'); print('✅ Token found, proceeding with login');
LoginTokenResponseModel tokenModel = LoginTokenResponseModel(accessToken: tokenFormWeb); LoginTokenResponseModel tokenModel = LoginTokenResponseModel(accessToken: token);
await DataPreference.instance.saveLoginToken(tokenModel); await DataPreference.instance.saveLoginToken(tokenModel);
_freshUserProfile(); _freshUserProfile();
return; return;
} }
/// Get token from x-app-sdk (web only)
Future<String?> _getTokenFromSDK() async {
if (!kIsWeb) {
print('🔍 SplashScreen - Not on web, skipping SDK token retrieval');
return null;
}
try {
print('🔍 SplashScreen - Attempting to get token from x-app-sdk...');
// Initialize SDK first
await webInitializeXAppSDK();
// Check if SDK is initialized
if (!webIsSDKInitialized()) {
print('⚠️ SplashScreen - SDK not initialized, skipping');
return null;
}
// Get token from SDK
final token = await webGetToken();
if (token != null && token.isNotEmpty) {
print('✅ SplashScreen - Token retrieved from x-app-sdk: ${token.substring(0, 8)}...');
return token;
} else {
final error = webGetLastError();
print('❌ SplashScreen - Failed to get token from SDK: $error');
return null;
}
} catch (e) {
print('❌ SplashScreen - Error getting token from SDK: $e');
return null;
}
}
Future<void> _freshUserProfile() async { Future<void> _freshUserProfile() async {
showLoading(); showLoading();
final response = await client.getUserProfile(); final response = await client.getUserProfile();
...@@ -70,10 +109,9 @@ class SplashScreenViewModel extends RestfulApiViewModel { ...@@ -70,10 +109,9 @@ class SplashScreenViewModel extends RestfulApiViewModel {
} }
void _directionWhenTokenInvalid() { void _directionWhenTokenInvalid() {
// TODO: handle later
Get.toNamed(onboardingScreen); Get.toNamed(onboardingScreen);
// if (kIsWeb) { // if (kIsWeb) {
// print('❌ No token found on web, cannot proceed'); // print('❌ No token found on web, closing app');
// webCloseApp({ // webCloseApp({
// 'message': 'No token found, cannot proceed', // 'message': 'No token found, cannot proceed',
// 'timestamp': DateTime.now().millisecondsSinceEpoch, // 'timestamp': DateTime.now().millisecondsSinceEpoch,
......
import 'package:flutter/foundation.dart';
import 'web_helper.dart';
/// Example usage of closeApp functionality
class CloseAppExample {
/// Close app without returning any data
static void closeAppSimple() {
if (kIsWeb) {
print('🚪 Closing app (simple)...');
webCloseApp();
} else {
print('⚠️ closeApp only works on web platform');
}
}
/// Close app with success data
static void closeAppWithSuccess() {
if (kIsWeb) {
print('🚪 Closing app with success data...');
webCloseApp({
'result': 'success',
'message': 'Operation completed successfully',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
} else {
print('⚠️ closeApp only works on web platform');
}
}
/// Close app with error data
static void closeAppWithError(String errorMessage) {
if (kIsWeb) {
print('🚪 Closing app with error data...');
webCloseApp({
'result': 'error',
'message': errorMessage,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
} else {
print('⚠️ closeApp only works on web platform');
}
}
/// Close app with custom data
static void closeAppWithCustomData(Map<String, dynamic> customData) {
if (kIsWeb) {
print('🚪 Closing app with custom data...');
webCloseApp({
'result': 'custom',
'data': customData,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
} else {
print('⚠️ closeApp only works on web platform');
}
}
/// Example: Close app after successful payment
static void closeAppAfterPayment({
required String transactionId,
required double amount,
required String currency,
}) {
if (kIsWeb) {
print('🚪 Closing app after payment...');
webCloseApp({
'result': 'payment_success',
'transactionId': transactionId,
'amount': amount,
'currency': currency,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
} else {
print('⚠️ closeApp only works on web platform');
}
}
/// Example: Close app after form submission
static void closeAppAfterFormSubmission({
required String formType,
required Map<String, dynamic> formData,
}) {
if (kIsWeb) {
print('🚪 Closing app after form submission...');
webCloseApp({
'result': 'form_submitted',
'formType': formType,
'formData': formData,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
} else {
print('⚠️ closeApp only works on web platform');
}
}
}
import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/configs/url_params.dart';
import 'web_helper.dart';
/// Web-specific initialization and configuration
class WebAppInitializer {
static final WebAppInitializer _instance = WebAppInitializer._internal();
factory WebAppInitializer() => _instance;
WebAppInitializer._internal();
// Ensure web initialization runs only once per app lifecycle
static bool _didInit = false;
// Ensure we only start one polling sequence for SDK data
static bool _startedSdkPolling = false;
/// Initialize all web-specific features
static Future<void> initialize() async {
if (!kIsWeb) return;
if (_didInit) {
// Prevent re-initialization on hot reload / route changes
return;
}
_didInit = true;
print('🌐 Initializing web-specific features...');
// Handle URL parameters
_handleWebUrlParams();
// Initialize x-app-sdk
_initializeXAppSDK();
}
/// Handle URL parameters for web
static void _handleWebUrlParams() {
print('🔍 Handling web URL parameters...');
final uri = Uri.base;
print('🔍 Current URI: ${uri.toString()}');
final token = uri.queryParameters['token'];
final userId = uri.queryParameters['userId'];
print('🔍 Web URL Params: {token: $token, user_id: $userId}');
if (token != null && token.isNotEmpty) {
UrlParams.setToken(token);
UrlParams.setUserId(userId);
print('✅ Token set from URL: $token');
print('🔍 UrlParams after set: ${UrlParams.allParams}');
// Clean URL to remove query params
webReplaceUrl('/');
} else {
print('❌ No token found in URL parameters');
}
}
/// Initialize x-app-sdk service
static void _initializeXAppSDK() {
print('🔍 Initializing x-app-sdk...');
// Check if x-app-sdk is available from Super App
final isSDKAvailable = webIsSDKAvailable();
print('🔍 XAppSDK available from Super App: $isSDKAvailable');
// Always try to initialize once (no-op on non-web/stub)
webInitializeXAppSDK().then((_) {
print('✅ XAppSDK service initialized');
// Only poll for data if SDK is actually available from host
if (isSDKAvailable) {
if (_startedSdkPolling) return;
_startedSdkPolling = true;
// Wait a bit for JavaScript to initialize and then check for data
_checkForAppHostData(0); // Start with retry count 0
} else {
print('ℹ️ XAppSDK not available – skipping polling outside Super App.');
print('💡 Tip: Test with URL params, e.g. ?token=test123&userId=user123');
}
}).catchError((error) {
print('❌ Error initializing XAppSDK: $error');
});
}
/// Check for app host data with retry mechanism (max 3 retries)
static void _checkForAppHostData(int retryCount) {
print('🔍 Checking for app host data... (attempt ${retryCount + 1}/4)');
// Wait a bit for JavaScript to initialize
Future.delayed(const Duration(milliseconds: 1000), () {
final token = webGetAppHostToken();
final user = webGetAppHostUser();
final error = webGetAppHostError();
final isReady = webIsAppHostDataReady();
print('🔍 Data check result:');
print(' Token: ${token != null ? '***${token.substring(0, 8)}...' : 'null'}');
print(' User: ${user?.toString() ?? 'null'}');
print(' Error: ${error ?? 'null'}');
print(' Ready: $isReady');
if (token != null && token.isNotEmpty) {
print(' Token from Super App: ${token.substring(0, 8)}...');
UrlParams.setToken(token);
if (user != null) {
print(' User from Super App: $user');
UrlParams.setUserId(user['id']?.toString());
// Store user data for later use
webStoreAppHostData(token, user);
}
} else if (error != null) {
print(' Error from Super App: $error');
} else if (!isReady && retryCount < 3) {
print(' App host data not ready yet, will retry... (${retryCount + 1}/3)');
// Retry after a longer delay
Future.delayed(const Duration(milliseconds: 2000), () {
_checkForAppHostData(retryCount + 1);
});
} else if (retryCount >= 3) {
print(' Max retries reached (3), giving up on Super App data');
print('💡 You can test with URL parameters: ?token=test123&user={"id":"user123"}');
} else {
print(' No token found from Super App');
}
});
}
}
export 'web_helper_stub.dart' export 'web_helper_stub.dart'
if (dart.library.html) 'web_helper_web.dart'; if (dart.library.html) 'web_helper_web.dart';
// Stub implementations for non-web platforms // Stub implementations for non-web platforms
void webReplaceUrl(String path) { /// Initialize x-app-sdk service (no-op on non-web)
// no-op on non-web
}
void webClearStorage() {
// no-op on non-web
}
/// Get token from app host via x-app-sdk
String? webGetAppHostToken() {
return null;
}
/// Get user info from app host via x-app-sdk
Map<String, dynamic>? webGetAppHostUser() {
return null;
}
/// Check if app host data is ready
bool webIsAppHostDataReady() {
return false;
}
/// Get error message from app host
String? webGetAppHostError() {
return null;
}
/// Initialize x-app-sdk service
Future<void> webInitializeXAppSDK() async { Future<void> webInitializeXAppSDK() async {
// no-op on non-web // no-op on non-web
} }
/// Store app host data /// Get token from x-app-sdk (no-op on non-web)
void webStoreAppHostData(String token, Map<String, dynamic>? user) { Future<String?> webGetToken() async {
// no-op on non-web return null;
} }
/// Clear app host data /// Close app and return to Super App (no-op on non-web)
void webClearAppHostData() { Future<void> webCloseApp([Map<String, dynamic>? data]) async {
// no-op on non-web // no-op on non-web
} }
/// Execute JavaScript in the web context /// Check if x-app-sdk is initialized (no-op on non-web)
Future<dynamic> webExecuteJavaScript(String script) async { bool webIsSDKInitialized() {
return null; return false;
}
/// Call x-app-sdk method if available
Future<dynamic> webCallXAppSDKMethod(String methodName, [List<dynamic>? args]) async {
return null;
} }
/// Get user info by key from app host /// Get cached token (no-op on non-web)
Future<dynamic> webGetUserInfoByKey(String key) async { String? webGetCachedToken() {
return null; return null;
} }
/// Get token asynchronously from app host /// Get last error message (no-op on non-web)
Future<String?> webGetTokenAsync() async { String? webGetLastError() {
return null; return null;
} }
/// Check if x-app-sdk is available from Super App /// Clear token cache (no-op on non-web)
bool webIsSDKAvailable() { void webClearTokenCache() {
return false; // no-op on non-web
} }
/// Close app and return to Super App /// Reset SDK service (no-op on non-web)
void webCloseApp([Map<String, dynamic>? data]) { void webResetSDK() {
// no-op on non-web // no-op on non-web
} }
// Web-specific implementations // Web-specific implementations for x-app-sdk
// ignore: avoid_web_libraries_in_flutter // ignore: avoid_web_libraries_in_flutter
import 'dart:convert'; import 'dart:convert';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'x_app_sdk_service.dart'; import 'x_app_sdk_service.dart';
void webReplaceUrl(String path) { /// Web-specific helper functions for x-app-sdk integration
try {
final origin = html.window.location.origin;
final newUrl = '$origin$path';
html.window.history.replaceState(null, html.document.title ?? '', newUrl);
} catch (_) {}
}
void webClearStorage() {
try {
html.window.localStorage.clear();
html.window.sessionStorage.clear();
// Optionally clear caches if any
} catch (_) {}
}
/// Get token from app host via x-app-sdk
String? webGetAppHostToken() {
try {
return XAppSDKService().getToken();
} catch (e) {
print('❌ Error getting app host token: $e');
return null;
}
}
/// Get user info from app host via x-app-sdk
Map<String, dynamic>? webGetAppHostUser() {
try {
return XAppSDKService().getUser();
} catch (e) {
print('❌ Error getting app host user: $e');
return null;
}
}
/// Check if app host data is ready
bool webIsAppHostDataReady() {
try {
return XAppSDKService().isServiceReady;
} catch (e) {
print('❌ Error checking app host data ready: $e');
return false;
}
}
/// Get error message from app host
String? webGetAppHostError() {
try {
return XAppSDKService().getErrorMessage();
} catch (e) {
print('❌ Error getting app host error: $e');
return null;
}
}
/// Initialize x-app-sdk service /// Initialize x-app-sdk service
Future<void> webInitializeXAppSDK() async { Future<void> webInitializeXAppSDK() async {
try { try {
await XAppSDKService().initialize(); await XAppSDKService().initialize();
XAppSDKService().listenForUpdates();
} catch (e) { } catch (e) {
print('❌ Error initializing x-app-sdk: $e'); print('❌ Error initializing x-app-sdk: $e');
} }
} }
/// Store app host data /// Get token from x-app-sdk
void webStoreAppHostData(String token, Map<String, dynamic>? user) { Future<String?> webGetToken() async {
try { try {
XAppSDKService().storeData(token, user); return await XAppSDKService().getToken();
} catch (e) { } catch (e) {
print('❌ Error storing app host data: $e'); print('❌ Error getting token: $e');
} return null;
}
/// Clear app host data
void webClearAppHostData() {
try {
XAppSDKService().clearData();
} catch (e) {
print('❌ Error clearing app host data: $e');
} }
} }
/// Execute JavaScript in the web context /// Close app and return to Super App
Future<dynamic> webExecuteJavaScript(String script) async { Future<void> webCloseApp([Map<String, dynamic>? data]) async {
try { try {
// For now, we'll use a simpler approach await XAppSDKService().closeApp(data);
// This method is mainly for future extensibility
print('⚠️ webExecuteJavaScript is not fully implemented yet');
return null;
} catch (e) { } catch (e) {
print('❌ Error executing JavaScript: $e'); print('❌ Error closing app: $e');
return null;
} }
} }
/// Call x-app-sdk method if available /// Check if x-app-sdk is initialized
Future<dynamic> webCallXAppSDKMethod(String methodName, [List<dynamic>? args]) async { bool webIsSDKInitialized() {
try { try {
return await XAppSDKService().callSDKMethod(methodName, args); return XAppSDKService().isInitialized;
} catch (e) { } catch (e) {
print('❌ Error calling x-app-sdk method $methodName: $e'); print('❌ Error checking SDK status: $e');
return null; return false;
} }
} }
/// Get user info by key from app host /// Get cached token
Future<dynamic> webGetUserInfoByKey(String key) async { String? webGetCachedToken() {
try { try {
return await XAppSDKService().getUserInfo(key); return XAppSDKService().cachedToken;
} catch (e) { } catch (e) {
print('❌ Error getting user info by key $key: $e'); print('❌ Error getting cached token: $e');
return null; return null;
} }
} }
/// Get token asynchronously from app host /// Get last error message
Future<String?> webGetTokenAsync() async { String? webGetLastError() {
try { try {
return await XAppSDKService().getTokenAsync(); return XAppSDKService().lastError;
} catch (e) { } catch (e) {
print('❌ Error getting token async: $e'); print('❌ Error getting last error: $e');
return null; return null;
} }
} }
/// Check if x-app-sdk is available from Super App /// Clear token cache
bool webIsSDKAvailable() { void webClearTokenCache() {
try { try {
return XAppSDKService().isSDKAvailable(); XAppSDKService().clearToken();
} catch (e) { } catch (e) {
print('❌ Error checking SDK availability: $e'); print('❌ Error clearing token cache: $e');
return false;
} }
} }
/// Close app and return to Super App /// Reset SDK service
void webCloseApp([Map<String, dynamic>? data]) { void webResetSDK() {
try { try {
XAppSDKService().closeApp(data); XAppSDKService().reset();
} catch (e) { } catch (e) {
print('❌ Error closing app: $e'); print('❌ Error resetting SDK: $e');
} }
} }
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:universal_html/js_util.dart'; import 'package:universal_html/js_util.dart';
/// X-App-SDK Service for Flutter Web
/// Provides simple integration with x-app-sdk for token retrieval and app closing
class XAppSDKService { class XAppSDKService {
static final XAppSDKService _instance = XAppSDKService._internal(); static final XAppSDKService _instance = XAppSDKService._internal();
factory XAppSDKService() => _instance; factory XAppSDKService() => _instance;
XAppSDKService._internal(); XAppSDKService._internal();
String? _token; bool _isInitialized = false;
Map<String, dynamic>? _user; dynamic _sdkModule;
bool _isReady = false; String? _cachedToken;
String? _error; String? _lastError;
String? get token => _token; /// Check if SDK is available and initialized
Map<String, dynamic>? get user => _user; bool get isInitialized => _isInitialized;
bool get isReady => _isReady;
String? get error => _error;
/// Initialize x-app-sdk service and get data from app host /// Get cached token (if any)
String? get cachedToken => _cachedToken;
/// Get last error message
String? get lastError => _lastError;
/// Initialize the SDK service
Future<void> initialize() async { Future<void> initialize() async {
if (!kIsWeb) return; if (!kIsWeb) {
print('⚠️ XAppSDKService: Not running on web platform');
return;
}
try { try {
// Wait longer for the JavaScript to initialize print('🔍 XAppSDKService: Initializing...');
await Future.delayed(const Duration(milliseconds: 1000)); final module = await _loadSdkModule();
if (module == null) {
// Check if AppHostData is available in window _lastError = 'x-app-sdk module could not be loaded';
final appHostData = getProperty(html.window, 'AppHostData'); print('❌ XAppSDKService: $_lastError');
if (appHostData != null) { return;
final data = jsonDecode(appHostData.toString());
_token = data['token'];
_user = data['user'] != null ? Map<String, dynamic>.from(data['user']) : null;
_isReady = data['isReady'] ?? false;
_error = data['error'];
print('✅ XAppSDK Service initialized:');
print(' Token: ${_token != null ? '***${_token!.substring(_token!.length - 4)}' : 'null'}');
print(' User: ${_user?.toString() ?? 'null'}');
print(' Ready: $_isReady');
if (_error != null) {
print(' Error: $_error');
}
} else {
print(' AppHostData not found in window, trying fallback...');
await _tryFallbackMethod();
} }
_sdkModule = module;
_isInitialized = true;
print('✅ XAppSDKService: Initialized successfully');
} catch (e) { } catch (e) {
print(' Error initializing XAppSDK Service: $e'); _lastError = 'Failed to initialize SDK: $e';
await _tryFallbackMethod(); print('❌ XAppSDKService: $_lastError');
} }
} }
/// Fallback method to get data from URL parameters or localStorage /// Get token from x-app-sdk
Future<void> _tryFallbackMethod() async { Future<String?> getToken() async {
if (!kIsWeb) {
print('⚠️ XAppSDKService: getToken() called on non-web platform');
return null;
}
if (!_isInitialized) {
print('⚠️ XAppSDKService: SDK not initialized, attempting to initialize...');
await initialize();
if (!_isInitialized) {
_lastError = 'SDK initialization failed';
return null;
}
}
try { try {
// Try to get from URL parameters first print('🔍 XAppSDKService: Getting token...');
final uri = Uri.base;
final token = uri.queryParameters['token']; final sdk = await _loadSdkModule();
final userStr = uri.queryParameters['user']; if (sdk == null) {
_lastError = 'x-app-sdk not available';
print('❌ XAppSDKService: $_lastError');
return null;
}
if (token != null && token.isNotEmpty) { final hasGetToken = _hasProperty(sdk, 'getToken');
_token = token; if (!hasGetToken) {
if (userStr != null && userStr.isNotEmpty) { _lastError = 'getToken method not found in SDK';
try { print('❌ XAppSDKService: $_lastError');
_user = jsonDecode(userStr); return null;
} catch (e) {
print(' Failed to parse user from URL: $e');
}
}
_isReady = true;
print(' Data loaded from URL parameters (fallback)');
return;
} }
// Try to get from localStorage // Execute getToken method from SDK
final storedToken = html.window.localStorage['app_host_token']; final result = await promiseToFuture(callMethod(sdk, 'getToken', []));
final storedUser = html.window.localStorage['app_host_user'];
if (result != null) {
final status = getProperty(result, 'status')?.toString();
final data = getProperty(result, 'data');
final message = getProperty(result, 'message');
final errorMessage = message?.toString();
if (storedToken != null && storedToken.isNotEmpty) { if (status == 'success' && data != null && data.toString().isNotEmpty) {
_token = storedToken; final tokenString = data.toString();
if (storedUser != null && storedUser.isNotEmpty) { _cachedToken = tokenString;
try { _lastError = null;
_user = jsonDecode(storedUser); final preview = tokenString.length > 8 ? tokenString.substring(0, 8) : tokenString;
} catch (e) { print('✅ XAppSDKService: Token retrieved successfully: $preview...');
print(' Failed to parse user from localStorage: $e'); return _cachedToken;
} } else {
final details = errorMessage?.isNotEmpty == true ? ' - $errorMessage' : '';
_lastError = 'SDK returned status: $status$details';
print('❌ XAppSDKService: $_lastError');
} }
_isReady = true;
print(' Data loaded from localStorage (fallback)');
} else { } else {
print(' No data found in URL parameters or localStorage'); _lastError = 'getToken returned null';
_error = 'No data available from app host'; print('❌ XAppSDKService: $_lastError');
} }
} catch (e) { } catch (e) {
print(' Error in fallback method: $e'); _lastError = 'Error getting token: $e';
_error = e.toString(); print('❌ XAppSDKService: $_lastError');
} }
}
/// Get token from app host return null;
String? getToken() {
return _token;
} }
/// Get user info from app host /// Close app and return to Super App
Map<String, dynamic>? getUser() { Future<void> closeApp([Map<String, dynamic>? data]) async {
return _user; if (!kIsWeb) {
} print('⚠️ XAppSDKService: closeApp() called on non-web platform');
return;
}
/// Check if service is ready if (!_isInitialized) {
bool get isServiceReady => _isReady; print('⚠️ XAppSDKService: SDK not initialized, attempting to initialize...');
await initialize();
if (!_isInitialized) {
print('❌ XAppSDKService: Cannot close app - SDK not initialized');
return;
}
}
/// Get error message if any try {
String? getErrorMessage() { print('🔍 XAppSDKService: Closing app...');
return _error;
} final sdk = await _loadSdkModule();
if (sdk == null) {
print('❌ XAppSDKService: x-app-sdk not available for closeApp');
return;
}
/// Clear stored data final hasCloseApp = _hasProperty(sdk, 'closeApp');
void clearData() { if (!hasCloseApp) {
_token = null; print('❌ XAppSDKService: closeApp method not found in SDK');
_user = null; return;
_isReady = false;
_error = null;
if (kIsWeb) {
try {
html.window.localStorage.remove('app_host_token');
html.window.localStorage.remove('app_host_user');
} catch (e) {
print(' Error clearing localStorage: $e');
} }
}
}
/// Store data for future use // Execute closeApp method from SDK with optional data
void storeData(String token, Map<String, dynamic>? user) { if (data != null) {
_token = token; callMethod(sdk, 'closeApp', [data]);
_user = user; print('✅ XAppSDKService: App closed with data: $data');
_isReady = true; } else {
_error = null; callMethod(sdk, 'closeApp', []);
print('✅ XAppSDKService: App closed successfully');
if (kIsWeb) { }
} catch (e) {
print('❌ XAppSDKService: Error closing app: $e');
// Fallback: try to close window or go back
try { try {
html.window.localStorage['app_host_token'] = token; if (html.window.history.length > 1) {
if (user != null) { html.window.history.back();
html.window.localStorage['app_host_user'] = jsonEncode(user); } else {
html.window.close();
} }
} catch (e) { print('✅ XAppSDKService: Fallback close executed');
print(' Error storing data: $e'); } catch (fallbackError) {
print('❌ XAppSDKService: Fallback close also failed: $fallbackError');
} }
} }
} }
/// Listen for data updates from app host /// Clear cached token
void listenForUpdates() { void clearToken() {
if (!kIsWeb) return; _cachedToken = null;
_lastError = null;
print('🧹 XAppSDKService: Token cache cleared');
}
/// Reset service state
void reset() {
_isInitialized = false;
_sdkModule = null;
_cachedToken = null;
_lastError = null;
try { try {
// Set up a periodic check for updates using js_util final loader = getProperty(html.window, '__xAppSdkLoader');
final intervalId = callMethod(html.window, 'setInterval', [ if (loader != null) {
allowInterop(() { if (_hasProperty(loader, 'resetCachedModule')) {
final appHostData = getProperty(html.window, 'AppHostData'); callMethod(loader, 'resetCachedModule', []);
if (appHostData != null) { }
final data = jsonDecode(appHostData.toString()); }
final newToken = data['token']; } catch (_) {}
final newUser = data['user']; print('🔄 XAppSDKService: Service reset');
final newReady = data['isReady'] ?? false; }
final newError = data['error'];
if (newReady && (newToken != _token || newUser != _user)) { Future<dynamic> _loadSdkModule() async {
_token = newToken; if (_sdkModule != null) {
_user = newUser != null ? Map<String, dynamic>.from(newUser) : null; return _sdkModule;
_isReady = newReady; }
_error = newError;
print('🔄 XAppSDK data updated from app host'); // Access loader API exposed in JS
} final loader = getProperty(html.window, '__xAppSdkLoader');
} if (loader == null) {
}), _lastError = 'x-app-sdk loader not found on global scope';
2000 // Check every 2 seconds print('❌ XAppSDKService: $_lastError');
]); return null;
// Store interval ID for potential cleanup
print(' Update listener set up with interval ID: $intervalId');
} catch (e) {
print(' Error setting up update listener: $e');
} }
}
/// Call x-app-sdk method directly from Super App final hasLoadFunction = _hasProperty(loader, 'loadXAppSdkModule');
Future<dynamic> callSDKMethod(String methodName, [List<dynamic>? args]) async { if (!hasLoadFunction) {
if (!kIsWeb) return null; _lastError = 'x-app-sdk loader missing loadXAppSdkModule';
print('❌ XAppSDKService: $_lastError');
return null;
}
try { try {
// Check if method is available from Super App final module = await promiseToFuture(callMethod(loader, 'loadXAppSdkModule', []));
final methodExists = getProperty(html.window, methodName); if (module == null) {
if (methodExists == null) { _lastError = 'x-app-sdk module resolved to null';
print(' Method $methodName not available from Super App'); print('❌ XAppSDKService: $_lastError');
return null; return null;
} }
// Call method directly using callMethod final source = getProperty(module, '__xAppSdkSource');
if (methodName == 'getToken') { if (source != null) {
return await promiseToFuture(callMethod(html.window, 'getToken', [])); print('🔗 XAppSDKService: Module loaded from $source');
} else if (methodName == 'getInfo' && args != null && args.isNotEmpty) { }
return await promiseToFuture(callMethod(html.window, 'getInfo', [args[0]]));
} else { final hasGetToken = _hasProperty(module, 'getToken');
print(' Unsupported method: $methodName'); final hasCloseApp = _hasProperty(module, 'closeApp');
if (!hasGetToken || !hasCloseApp) {
_lastError = 'x-app-sdk module missing required exports';
print('❌ XAppSDKService: $_lastError');
return null; return null;
} }
_sdkModule = module;
return _sdkModule;
} catch (e) { } catch (e) {
print(' Error calling SDK method $methodName: $e'); _lastError = 'Failed to load x-app-sdk module: $e';
print('❌ XAppSDKService: $_lastError');
return null; return null;
} }
} }
/// Get user info by key from Super App bool _hasProperty(dynamic target, String name) {
Future<dynamic> getUserInfo(String key) async {
return await callSDKMethod('getInfo', [key]);
}
/// Get token from Super App
Future<String?> getTokenAsync() async {
return await callSDKMethod('getToken');
}
/// Check if x-app-sdk is available from Super App
bool isSDKAvailable() {
if (!kIsWeb) return false;
try { try {
final getToken = getProperty(html.window, 'getToken'); final value = getProperty(target, name);
final getInfo = getProperty(html.window, 'getInfo'); return value != null;
return getToken != null && getInfo != null; } catch (_) {
} catch (e) {
return false; return false;
} }
} }
/// Close app and return to Super App
void closeApp([Map<String, dynamic>? data]) {
if (!kIsWeb) return;
try {
print('🚪 Closing app and returning to Super App...');
if (data != null) {
print('📤 Data to return: $data');
}
// Call the JavaScript closeApp function
callMethod(html.window, 'closeApp', [data]);
print(' closeApp called successfully');
} catch (e) {
print(' Error calling closeApp: $e');
// Fallback: try to close the window
try {
html.window.close();
} catch (fallbackError) {
print(' Fallback close also failed: $fallbackError');
}
}
}
} }
../acorn/bin/acorn
\ No newline at end of file
../@microsoft/api-extractor/bin/api-extractor
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment