Commit 956c501c authored by DatHV's avatar DatHV
Browse files

update build x-app-sdk

parent 9bb8aadd
...@@ -24,23 +24,22 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState { ...@@ -24,23 +24,22 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_viewModel.checkUpdateApp(); _viewModel.checkUpdateResponse = (data) {
_viewModel.infoAppUpdate.listen((response) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final list = response.data?.updateRequest; final updateData = (data?.updateRequest ?? []).firstOrNull;
if (list == null || list.isEmpty) { if (updateData == null) {
_viewModel.makeDataFollowInitApp(); _viewModel.makeDataFollowInitApp();
return; return;
} }
var result = response?.data?.updateRequest?.first; var status = updateData?.status ?? UpdateStatus.none;
var status = result?.status ?? UpdateStatus.none; if (status == UpdateStatus.none) {
if (result == null || status == UpdateStatus.none) { Get.offAllNamed(onboardingScreen);
_navigateToBeforCheckUpdate();
} else { } else {
_showSuggestUpdateAlert(result); _showSuggestUpdateAlert(updateData);
} }
}); });
}); };
_viewModel.checkUpdateApp();
} }
@override @override
...@@ -56,14 +55,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState { ...@@ -56,14 +55,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
image: DecorationImage(image: AssetImage("assets/images/splash_screen.png"), fit: BoxFit.cover), image: DecorationImage(image: AssetImage("assets/images/splash_screen.png"), fit: BoxFit.cover),
), ),
), ),
Center(child: CircularProgressIndicator()),
Obx(() {
if (_viewModel.isLoading.value) {
return Center(child: CircularProgressIndicator());
} else {
return Container(width: double.infinity, height: double.infinity, color: Colors.black.withOpacity(0.5));
}
}),
], ],
), ),
); );
...@@ -77,10 +69,6 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState { ...@@ -77,10 +69,6 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
} }
} }
void _navigateToBeforCheckUpdate() {
Get.offAllNamed(onboardingScreen);
}
void _showSuggestUpdateAlert(CheckUpdateResponseModel data) { void _showSuggestUpdateAlert(CheckUpdateResponseModel data) {
final buttons = data.status == UpdateStatus.force final buttons = data.status == UpdateStatus.force
? [AlertButton( ? [AlertButton(
......
import 'dart:async';
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 '../../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 {
var infoAppUpdate = BaseResponseModel<UpdateResponseModel>().obs; void Function(UpdateResponseModel? data)? checkUpdateResponse;
var isLoading = false.obs; var _updateLink = '';
static const Duration _networkTimeout = Duration(seconds: 20);
static const Duration _sdkTimeout = Duration(seconds: 20);
void checkUpdateApp() { Future<void> checkUpdateApp() async {
showLoading(); try {
isLoading(true); final response = await client.checkUpdateApp().timeout(_networkTimeout);
client.checkUpdateApp().then((value) { _updateLink = response.data?.updateRequest?.firstOrNull?.updateLink.orEmpty ?? '';
infoAppUpdate.value = value; checkUpdateResponse?.call(response.data);
hideLoading(); } on Object catch (e) {
isLoading(false); debugPrint('⚠️ SplashScreen - checkUpdateApp failed: $e');
}); checkUpdateResponse?.call(null);
}
} }
Future<void> openLink() async { Future<void> openLink() async {
final updateLink = infoAppUpdate.value.data?.updateRequest?.first?.updateLink ?? ""; if (_updateLink.isEmpty) return;
if (updateLink.isEmpty) return; final Uri url = Uri.parse(_updateLink);
final Uri url = Uri.parse(updateLink);
if (await canLaunchUrl(url)) { if (await canLaunchUrl(url)) {
await launchUrl(url); await launchUrl(url);
} }
} }
Future<void> makeDataFollowInitApp() async { Future<void> makeDataFollowInitApp() async {
if (DataPreference.instance.logged) { try {
_freshUserProfile(); if (DataPreference.instance.logged) {
return; await _loadProfileAndNavigate();
} return;
final tokenFormWeb = UrlParams.getTokenForApi() ?? ""; }
print('🔍 SplashScreen - Token from URL: $tokenFormWeb');
if (tokenFormWeb.isEmpty) { final token = await _getTokenFromSDK();
if (token.orEmpty.isEmpty) {
_directionWhenTokenInvalid();
return;
}
await DataPreference.instance.saveLoginToken(LoginTokenResponseModel(accessToken: token));
await _loadProfileAndNavigate();
} on Object catch (e) {
debugPrint('❌ SplashScreen - makeDataFollowInitApp error: $e');
DataPreference.instance.clearLoginToken();
_directionWhenTokenInvalid(); _directionWhenTokenInvalid();
return;
} }
print('✅ Token found, proceeding with login');
LoginTokenResponseModel tokenModel = LoginTokenResponseModel(accessToken: tokenFormWeb);
await DataPreference.instance.saveLoginToken(tokenModel);
_freshUserProfile();
return;
} }
Future<void> _freshUserProfile() async { /// Get token from x-app-sdk (web only)
showLoading(); Future<String?> _getTokenFromSDK() async {
final response = await client.getUserProfile(); if (!kIsWeb) {
hideLoading(); print('🔍 SplashScreen - Not on web, skipping SDK token retrieval');
final userProfile = response.data; return null;
if (response.isSuccess && userProfile != null) { }
_freshDataAndToMainScreen(userProfile); try {
} else { print('🔍 SplashScreen - Attempting to get token from x-app-sdk...');
await webInitializeXAppSDK().timeout(_sdkTimeout);
if (!webIsSDKInitialized()) {
print('⚠️ SplashScreen - SDK not initialized, skipping');
return null;
}
// Get token from SDK
final token = await webGetToken().timeout(_sdkTimeout);
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> _loadProfileAndNavigate() async {
final profile = await _fetchUserProfile();
if (profile == null) {
DataPreference.instance.clearLoginToken(); DataPreference.instance.clearLoginToken();
_directionWhenTokenInvalid(); _directionWhenTokenInvalid();
return;
} }
await _prepareInitialData(profile);
_goToMain();
} }
void _directionWhenTokenInvalid() { void _directionWhenTokenInvalid() {
// TODO: handle later if (Get.currentRoute == onboardingScreen) return;
Get.toNamed(onboardingScreen); Get.offAllNamed(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,
...@@ -82,12 +116,44 @@ class SplashScreenViewModel extends RestfulApiViewModel { ...@@ -82,12 +116,44 @@ class SplashScreenViewModel extends RestfulApiViewModel {
// Get.toNamed(onboardingScreen); // Get.toNamed(onboardingScreen);
// } // }
} }
void _freshDataAndToMainScreen(ProfileResponseModel userProfile) async {
WidgetsBinding.instance.addPostFrameCallback((_) async { Future<ProfileResponseModel?> _fetchUserProfile() async {
await DataPreference.instance.saveUserProfile(userProfile); try {
await UserPointManager().fetchUserPoint(); final response = await client.getUserProfile().timeout(_networkTimeout);
await PopupManagerViewModel.instance.ensureLoaded(); if (response.isSuccess) {
Get.toNamed(mainScreen); return response.data;
}); }
debugPrint('⚠️ SplashScreen - getUserProfile failed: ${response.errorMessage ?? response.message}');
} on TimeoutException catch (_) {
debugPrint('⚠️ SplashScreen - getUserProfile timeout');
} catch (e) {
debugPrint('⚠️ SplashScreen - getUserProfile error: $e');
}
return null;
}
Future<void> _prepareInitialData(ProfileResponseModel profile) async {
await _runSafely(() => DataPreference.instance.saveUserProfile(profile), 'cache user profile');
await _runSafely(() => UserPointManager().fetchUserPoint(), 'fetch user point');
await _runSafely(() => PopupManagerViewModel.instance.ensureLoaded(), 'preload popups');
}
Future<void> _runSafely(Future<void> Function() task, String label) async {
try {
await task().timeout(_networkTimeout);
} on TimeoutException catch (_) {
debugPrint('⚠️ SplashScreen - $label timeout');
} catch (e) {
debugPrint('⚠️ SplashScreen - $label failed: $e');
}
}
void _goToMain() {
if (Get.currentRoute == mainScreen) return;
if (Get.currentRoute == onboardingScreen) {
Get.offNamed(mainScreen);
} else {
Get.offAllNamed(mainScreen);
}
} }
} }
\ No newline at end of file
import 'package:flutter/foundation.dart';
import '../networking/dio_http_service.dart';
import '../networking/restful_api_client.dart';
import '../networking/restful_api_client_all_request.dart';
class LogoutService {
LogoutService._();
static final RestfulAPIClient _client = RestfulAPIClient(DioHttpService().dio);
static Future<void> logout() async {
try {
await _client.logout();
} catch (e) {
if (kDebugMode) {
print('LogoutService.logout failed: $e');
}
}
}
}
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:mypoint_flutter_app/configs/constants.dart'; import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.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 '../model/auth/login_token_response_model.dart'; import '../model/auth/login_token_response_model.dart';
import '../networking/restful_api_viewmodel.dart'; import '../networking/restful_api_viewmodel.dart';
...@@ -19,7 +20,8 @@ class TokenRefreshService extends RestfulApiViewModel { ...@@ -19,7 +20,8 @@ class TokenRefreshService extends RestfulApiViewModel {
_callbacks.add(callback); _callbacks.add(callback);
if (_isRefreshing) return; if (_isRefreshing) return;
final token = DataPreference.instance.token; final token = DataPreference.instance.token;
if (token == null || token.isEmpty) { final refreshToken = DataPreference.instance.refreshToken;
if (token.orEmpty.isEmpty || refreshToken.orEmpty.isEmpty) {
_handleRefreshFailure(); _handleRefreshFailure();
return; return;
} }
......
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
../he/bin/he
\ No newline at end of file
../@babel/parser/bin/babel-parser.js
\ No newline at end of file
../resolve/bin/resolve
\ No newline at end of file
../semver/bin/semver.js
\ No newline at end of file
../typescript/bin/tsc
\ No newline at end of file
../typescript/bin/tsserver
\ No newline at end of file
This diff is collapsed.
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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