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 {
@override
void initState() {
super.initState();
_viewModel.checkUpdateApp();
_viewModel.infoAppUpdate.listen((response) {
_viewModel.checkUpdateResponse = (data) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final list = response.data?.updateRequest;
if (list == null || list.isEmpty) {
final updateData = (data?.updateRequest ?? []).firstOrNull;
if (updateData == null) {
_viewModel.makeDataFollowInitApp();
return;
}
var result = response?.data?.updateRequest?.first;
var status = result?.status ?? UpdateStatus.none;
if (result == null || status == UpdateStatus.none) {
_navigateToBeforCheckUpdate();
var status = updateData?.status ?? UpdateStatus.none;
if (status == UpdateStatus.none) {
Get.offAllNamed(onboardingScreen);
} else {
_showSuggestUpdateAlert(result);
_showSuggestUpdateAlert(updateData);
}
});
});
};
_viewModel.checkUpdateApp();
}
@override
......@@ -56,14 +55,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
image: DecorationImage(image: AssetImage("assets/images/splash_screen.png"), fit: BoxFit.cover),
),
),
Obx(() {
if (_viewModel.isLoading.value) {
return Center(child: CircularProgressIndicator());
} else {
return Container(width: double.infinity, height: double.infinity, color: Colors.black.withOpacity(0.5));
}
}),
Center(child: CircularProgressIndicator()),
],
),
);
......@@ -77,10 +69,6 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
}
}
void _navigateToBeforCheckUpdate() {
Get.offAllNamed(onboardingScreen);
}
void _showSuggestUpdateAlert(CheckUpdateResponseModel data) {
final buttons = data.status == UpdateStatus.force
? [AlertButton(
......
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.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_client_all_request.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/profile_response_model.dart';
import '../../web/web_helper_stub.dart';
import 'models/update_response_model.dart';
import '../../preference/data_preference.dart';
import '../../preference/point/point_manager.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../web/web_helper.dart';
import '../popup_manager/popup_manager_viewmodel.dart';
class SplashScreenViewModel extends RestfulApiViewModel {
var infoAppUpdate = BaseResponseModel<UpdateResponseModel>().obs;
var isLoading = false.obs;
void Function(UpdateResponseModel? data)? checkUpdateResponse;
var _updateLink = '';
static const Duration _networkTimeout = Duration(seconds: 20);
static const Duration _sdkTimeout = Duration(seconds: 20);
void checkUpdateApp() {
showLoading();
isLoading(true);
client.checkUpdateApp().then((value) {
infoAppUpdate.value = value;
hideLoading();
isLoading(false);
});
Future<void> checkUpdateApp() async {
try {
final response = await client.checkUpdateApp().timeout(_networkTimeout);
_updateLink = response.data?.updateRequest?.firstOrNull?.updateLink.orEmpty ?? '';
checkUpdateResponse?.call(response.data);
} on Object catch (e) {
debugPrint('⚠️ SplashScreen - checkUpdateApp failed: $e');
checkUpdateResponse?.call(null);
}
}
Future<void> openLink() async {
final updateLink = infoAppUpdate.value.data?.updateRequest?.first?.updateLink ?? "";
if (updateLink.isEmpty) return;
final Uri url = Uri.parse(updateLink);
if (_updateLink.isEmpty) return;
final Uri url = Uri.parse(_updateLink);
if (await canLaunchUrl(url)) {
await launchUrl(url);
}
}
Future<void> makeDataFollowInitApp() async {
if (DataPreference.instance.logged) {
_freshUserProfile();
return;
}
final tokenFormWeb = UrlParams.getTokenForApi() ?? "";
print('🔍 SplashScreen - Token from URL: $tokenFormWeb');
if (tokenFormWeb.isEmpty) {
try {
if (DataPreference.instance.logged) {
await _loadProfileAndNavigate();
return;
}
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();
return;
}
print('✅ Token found, proceeding with login');
LoginTokenResponseModel tokenModel = LoginTokenResponseModel(accessToken: tokenFormWeb);
await DataPreference.instance.saveLoginToken(tokenModel);
_freshUserProfile();
return;
}
Future<void> _freshUserProfile() async {
showLoading();
final response = await client.getUserProfile();
hideLoading();
final userProfile = response.data;
if (response.isSuccess && userProfile != null) {
_freshDataAndToMainScreen(userProfile);
} else {
/// 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...');
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();
_directionWhenTokenInvalid();
return;
}
await _prepareInitialData(profile);
_goToMain();
}
void _directionWhenTokenInvalid() {
// TODO: handle later
Get.toNamed(onboardingScreen);
if (Get.currentRoute == onboardingScreen) return;
Get.offAllNamed(onboardingScreen);
// if (kIsWeb) {
// print('❌ No token found on web, cannot proceed');
// print('❌ No token found on web, closing app');
// webCloseApp({
// 'message': 'No token found, cannot proceed',
// 'timestamp': DateTime.now().millisecondsSinceEpoch,
......@@ -82,12 +116,44 @@ class SplashScreenViewModel extends RestfulApiViewModel {
// Get.toNamed(onboardingScreen);
// }
}
void _freshDataAndToMainScreen(ProfileResponseModel userProfile) async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await DataPreference.instance.saveUserProfile(userProfile);
await UserPointManager().fetchUserPoint();
await PopupManagerViewModel.instance.ensureLoaded();
Get.toNamed(mainScreen);
});
Future<ProfileResponseModel?> _fetchUserProfile() async {
try {
final response = await client.getUserProfile().timeout(_networkTimeout);
if (response.isSuccess) {
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: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 '../model/auth/login_token_response_model.dart';
import '../networking/restful_api_viewmodel.dart';
......@@ -19,7 +20,8 @@ class TokenRefreshService extends RestfulApiViewModel {
_callbacks.add(callback);
if (_isRefreshing) return;
final token = DataPreference.instance.token;
if (token == null || token.isEmpty) {
final refreshToken = DataPreference.instance.refreshToken;
if (token.orEmpty.isEmpty || refreshToken.orEmpty.isEmpty) {
_handleRefreshFailure();
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'
if (dart.library.html) 'web_helper_web.dart';
// Stub implementations for non-web platforms
void webReplaceUrl(String path) {
// 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
/// Initialize x-app-sdk service (no-op on non-web)
Future<void> webInitializeXAppSDK() async {
// no-op on non-web
}
/// Store app host data
void webStoreAppHostData(String token, Map<String, dynamic>? user) {
// no-op on non-web
/// Get token from x-app-sdk (no-op on non-web)
Future<String?> webGetToken() async {
return null;
}
/// Clear app host data
void webClearAppHostData() {
/// Close app and return to Super App (no-op on non-web)
Future<void> webCloseApp([Map<String, dynamic>? data]) async {
// no-op on non-web
}
/// Execute JavaScript in the web context
Future<dynamic> webExecuteJavaScript(String script) async {
return null;
}
/// Call x-app-sdk method if available
Future<dynamic> webCallXAppSDKMethod(String methodName, [List<dynamic>? args]) async {
return null;
/// Check if x-app-sdk is initialized (no-op on non-web)
bool webIsSDKInitialized() {
return false;
}
/// Get user info by key from app host
Future<dynamic> webGetUserInfoByKey(String key) async {
/// Get cached token (no-op on non-web)
String? webGetCachedToken() {
return null;
}
/// Get token asynchronously from app host
Future<String?> webGetTokenAsync() async {
/// Get last error message (no-op on non-web)
String? webGetLastError() {
return null;
}
/// Check if x-app-sdk is available from Super App
bool webIsSDKAvailable() {
return false;
/// Clear token cache (no-op on non-web)
void webClearTokenCache() {
// no-op on non-web
}
/// Close app and return to Super App
void webCloseApp([Map<String, dynamic>? data]) {
/// Reset SDK service (no-op on non-web)
void webResetSDK() {
// no-op on non-web
}
// Web-specific implementations
// Web-specific implementations for x-app-sdk
// ignore: avoid_web_libraries_in_flutter
import 'dart:convert';
import 'package:universal_html/html.dart' as html;
import 'x_app_sdk_service.dart';
void webReplaceUrl(String path) {
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;
}
}
/// Web-specific helper functions for x-app-sdk integration
/// Initialize x-app-sdk service
Future<void> webInitializeXAppSDK() async {
try {
await XAppSDKService().initialize();
XAppSDKService().listenForUpdates();
} catch (e) {
print('❌ Error initializing x-app-sdk: $e');
}
}
/// Store app host data
void webStoreAppHostData(String token, Map<String, dynamic>? user) {
/// Get token from x-app-sdk
Future<String?> webGetToken() async {
try {
XAppSDKService().storeData(token, user);
return await XAppSDKService().getToken();
} catch (e) {
print('❌ Error storing app host data: $e');
}
}
/// Clear app host data
void webClearAppHostData() {
try {
XAppSDKService().clearData();
} catch (e) {
print('❌ Error clearing app host data: $e');
print('❌ Error getting token: $e');
return null;
}
}
/// Execute JavaScript in the web context
Future<dynamic> webExecuteJavaScript(String script) async {
/// Close app and return to Super App
Future<void> webCloseApp([Map<String, dynamic>? data]) async {
try {
// For now, we'll use a simpler approach
// This method is mainly for future extensibility
print('⚠️ webExecuteJavaScript is not fully implemented yet');
return null;
await XAppSDKService().closeApp(data);
} catch (e) {
print('❌ Error executing JavaScript: $e');
return null;
print('❌ Error closing app: $e');
}
}
/// Call x-app-sdk method if available
Future<dynamic> webCallXAppSDKMethod(String methodName, [List<dynamic>? args]) async {
/// Check if x-app-sdk is initialized
bool webIsSDKInitialized() {
try {
return await XAppSDKService().callSDKMethod(methodName, args);
return XAppSDKService().isInitialized;
} catch (e) {
print('❌ Error calling x-app-sdk method $methodName: $e');
return null;
print('❌ Error checking SDK status: $e');
return false;
}
}
/// Get user info by key from app host
Future<dynamic> webGetUserInfoByKey(String key) async {
/// Get cached token
String? webGetCachedToken() {
try {
return await XAppSDKService().getUserInfo(key);
return XAppSDKService().cachedToken;
} catch (e) {
print('❌ Error getting user info by key $key: $e');
print('❌ Error getting cached token: $e');
return null;
}
}
/// Get token asynchronously from app host
Future<String?> webGetTokenAsync() async {
/// Get last error message
String? webGetLastError() {
try {
return await XAppSDKService().getTokenAsync();
return XAppSDKService().lastError;
} catch (e) {
print('❌ Error getting token async: $e');
print('❌ Error getting last error: $e');
return null;
}
}
/// Check if x-app-sdk is available from Super App
bool webIsSDKAvailable() {
/// Clear token cache
void webClearTokenCache() {
try {
return XAppSDKService().isSDKAvailable();
XAppSDKService().clearToken();
} catch (e) {
print('❌ Error checking SDK availability: $e');
return false;
print('❌ Error clearing token cache: $e');
}
}
/// Close app and return to Super App
void webCloseApp([Map<String, dynamic>? data]) {
/// Reset SDK service
void webResetSDK() {
try {
XAppSDKService().closeApp(data);
XAppSDKService().reset();
} catch (e) {
print('❌ Error closing app: $e');
print('❌ Error resetting SDK: $e');
}
}
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:universal_html/html.dart' as html;
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 {
static final XAppSDKService _instance = XAppSDKService._internal();
factory XAppSDKService() => _instance;
XAppSDKService._internal();
String? _token;
Map<String, dynamic>? _user;
bool _isReady = false;
String? _error;
bool _isInitialized = false;
dynamic _sdkModule;
String? _cachedToken;
String? _lastError;
String? get token => _token;
Map<String, dynamic>? get user => _user;
bool get isReady => _isReady;
String? get error => _error;
/// Check if SDK is available and initialized
bool get isInitialized => _isInitialized;
/// 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 {
if (!kIsWeb) return;
if (!kIsWeb) {
print('⚠️ XAppSDKService: Not running on web platform');
return;
}
try {
// Wait longer for the JavaScript to initialize
await Future.delayed(const Duration(milliseconds: 1000));
// Check if AppHostData is available in window
final appHostData = getProperty(html.window, 'AppHostData');
if (appHostData != null) {
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();
print('🔍 XAppSDKService: Initializing...');
final module = await _loadSdkModule();
if (module == null) {
_lastError = 'x-app-sdk module could not be loaded';
print('❌ XAppSDKService: $_lastError');
return;
}
_sdkModule = module;
_isInitialized = true;
print('✅ XAppSDKService: Initialized successfully');
} catch (e) {
print(' Error initializing XAppSDK Service: $e');
await _tryFallbackMethod();
_lastError = 'Failed to initialize SDK: $e';
print('❌ XAppSDKService: $_lastError');
}
}
/// Fallback method to get data from URL parameters or localStorage
Future<void> _tryFallbackMethod() async {
/// Get token from x-app-sdk
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 to get from URL parameters first
final uri = Uri.base;
final token = uri.queryParameters['token'];
final userStr = uri.queryParameters['user'];
print('🔍 XAppSDKService: Getting token...');
final sdk = await _loadSdkModule();
if (sdk == null) {
_lastError = 'x-app-sdk not available';
print('❌ XAppSDKService: $_lastError');
return null;
}
if (token != null && token.isNotEmpty) {
_token = token;
if (userStr != null && userStr.isNotEmpty) {
try {
_user = jsonDecode(userStr);
} catch (e) {
print(' Failed to parse user from URL: $e');
}
}
_isReady = true;
print(' Data loaded from URL parameters (fallback)');
return;
final hasGetToken = _hasProperty(sdk, 'getToken');
if (!hasGetToken) {
_lastError = 'getToken method not found in SDK';
print('❌ XAppSDKService: $_lastError');
return null;
}
// Try to get from localStorage
final storedToken = html.window.localStorage['app_host_token'];
final storedUser = html.window.localStorage['app_host_user'];
// Execute getToken method from SDK
final result = await promiseToFuture(callMethod(sdk, 'getToken', []));
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) {
_token = storedToken;
if (storedUser != null && storedUser.isNotEmpty) {
try {
_user = jsonDecode(storedUser);
} catch (e) {
print(' Failed to parse user from localStorage: $e');
}
if (status == 'success' && data != null && data.toString().isNotEmpty) {
final tokenString = data.toString();
_cachedToken = tokenString;
_lastError = null;
final preview = tokenString.length > 8 ? tokenString.substring(0, 8) : tokenString;
print('✅ XAppSDKService: Token retrieved successfully: $preview...');
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 {
print(' No data found in URL parameters or localStorage');
_error = 'No data available from app host';
_lastError = 'getToken returned null';
print('❌ XAppSDKService: $_lastError');
}
} catch (e) {
print(' Error in fallback method: $e');
_error = e.toString();
_lastError = 'Error getting token: $e';
print('❌ XAppSDKService: $_lastError');
}
}
/// Get token from app host
String? getToken() {
return _token;
return null;
}
/// Get user info from app host
Map<String, dynamic>? getUser() {
return _user;
}
/// Close app and return to Super App
Future<void> closeApp([Map<String, dynamic>? data]) async {
if (!kIsWeb) {
print('⚠️ XAppSDKService: closeApp() called on non-web platform');
return;
}
/// Check if service is ready
bool get isServiceReady => _isReady;
if (!_isInitialized) {
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
String? getErrorMessage() {
return _error;
}
try {
print('🔍 XAppSDKService: Closing app...');
final sdk = await _loadSdkModule();
if (sdk == null) {
print('❌ XAppSDKService: x-app-sdk not available for closeApp');
return;
}
/// Clear stored data
void clearData() {
_token = null;
_user = null;
_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');
final hasCloseApp = _hasProperty(sdk, 'closeApp');
if (!hasCloseApp) {
print('❌ XAppSDKService: closeApp method not found in SDK');
return;
}
}
}
/// Store data for future use
void storeData(String token, Map<String, dynamic>? user) {
_token = token;
_user = user;
_isReady = true;
_error = null;
if (kIsWeb) {
// Execute closeApp method from SDK with optional data
if (data != null) {
callMethod(sdk, 'closeApp', [data]);
print('✅ XAppSDKService: App closed with data: $data');
} else {
callMethod(sdk, 'closeApp', []);
print('✅ XAppSDKService: App closed successfully');
}
} catch (e) {
print('❌ XAppSDKService: Error closing app: $e');
// Fallback: try to close window or go back
try {
html.window.localStorage['app_host_token'] = token;
if (user != null) {
html.window.localStorage['app_host_user'] = jsonEncode(user);
if (html.window.history.length > 1) {
html.window.history.back();
} else {
html.window.close();
}
} catch (e) {
print(' Error storing data: $e');
print('✅ XAppSDKService: Fallback close executed');
} catch (fallbackError) {
print('❌ XAppSDKService: Fallback close also failed: $fallbackError');
}
}
}
/// Listen for data updates from app host
void listenForUpdates() {
if (!kIsWeb) return;
/// Clear cached token
void clearToken() {
_cachedToken = null;
_lastError = null;
print('🧹 XAppSDKService: Token cache cleared');
}
/// Reset service state
void reset() {
_isInitialized = false;
_sdkModule = null;
_cachedToken = null;
_lastError = null;
try {
// Set up a periodic check for updates using js_util
final intervalId = callMethod(html.window, 'setInterval', [
allowInterop(() {
final appHostData = getProperty(html.window, 'AppHostData');
if (appHostData != null) {
final data = jsonDecode(appHostData.toString());
final newToken = data['token'];
final newUser = data['user'];
final newReady = data['isReady'] ?? false;
final newError = data['error'];
final loader = getProperty(html.window, '__xAppSdkLoader');
if (loader != null) {
if (_hasProperty(loader, 'resetCachedModule')) {
callMethod(loader, 'resetCachedModule', []);
}
}
} catch (_) {}
print('🔄 XAppSDKService: Service reset');
}
if (newReady && (newToken != _token || newUser != _user)) {
_token = newToken;
_user = newUser != null ? Map<String, dynamic>.from(newUser) : null;
_isReady = newReady;
_error = newError;
print('🔄 XAppSDK data updated from app host');
}
}
}),
2000 // Check every 2 seconds
]);
// Store interval ID for potential cleanup
print(' Update listener set up with interval ID: $intervalId');
} catch (e) {
print(' Error setting up update listener: $e');
Future<dynamic> _loadSdkModule() async {
if (_sdkModule != null) {
return _sdkModule;
}
// 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';
print('❌ XAppSDKService: $_lastError');
return null;
}
}
/// Call x-app-sdk method directly from Super App
Future<dynamic> callSDKMethod(String methodName, [List<dynamic>? args]) async {
if (!kIsWeb) return null;
final hasLoadFunction = _hasProperty(loader, 'loadXAppSdkModule');
if (!hasLoadFunction) {
_lastError = 'x-app-sdk loader missing loadXAppSdkModule';
print('❌ XAppSDKService: $_lastError');
return null;
}
try {
// Check if method is available from Super App
final methodExists = getProperty(html.window, methodName);
if (methodExists == null) {
print(' Method $methodName not available from Super App');
final module = await promiseToFuture(callMethod(loader, 'loadXAppSdkModule', []));
if (module == null) {
_lastError = 'x-app-sdk module resolved to null';
print('❌ XAppSDKService: $_lastError');
return null;
}
// Call method directly using callMethod
if (methodName == 'getToken') {
return await promiseToFuture(callMethod(html.window, 'getToken', []));
} else if (methodName == 'getInfo' && args != null && args.isNotEmpty) {
return await promiseToFuture(callMethod(html.window, 'getInfo', [args[0]]));
} else {
print(' Unsupported method: $methodName');
final source = getProperty(module, '__xAppSdkSource');
if (source != null) {
print('🔗 XAppSDKService: Module loaded from $source');
}
final hasGetToken = _hasProperty(module, 'getToken');
final hasCloseApp = _hasProperty(module, 'closeApp');
if (!hasGetToken || !hasCloseApp) {
_lastError = 'x-app-sdk module missing required exports';
print('❌ XAppSDKService: $_lastError');
return null;
}
_sdkModule = module;
return _sdkModule;
} catch (e) {
print(' Error calling SDK method $methodName: $e');
_lastError = 'Failed to load x-app-sdk module: $e';
print('❌ XAppSDKService: $_lastError');
return null;
}
}
/// Get user info by key from Super App
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;
bool _hasProperty(dynamic target, String name) {
try {
final getToken = getProperty(html.window, 'getToken');
final getInfo = getProperty(html.window, 'getInfo');
return getToken != null && getInfo != null;
} catch (e) {
final value = getProperty(target, name);
return value != null;
} catch (_) {
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