Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Hoàng Văn Đạt
mypoint_flutter_app
Commits
a0bcdab2
Commit
a0bcdab2
authored
Oct 24, 2025
by
DatHV
Browse files
refactor. update logic,
parent
9f4cb968
Changes
35
Hide whitespace changes
Inline
Side-by-side
lib/screen/home/home_screen.dart
View file @
a0bcdab2
...
...
@@ -35,6 +35,7 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
@override
void
initState
()
{
super
.
initState
();
_headerHomeVM
.
freshData
();
runPopupCheck
(
DirectionalScreenName
.
home
);
}
...
...
@@ -240,7 +241,7 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
}
Future
<
void
>
_onRefresh
()
async
{
await
_viewModel
.
getSectionLayoutHome
();
await
_viewModel
.
getSectionLayoutHome
(
showLoading:
false
);
await
_viewModel
.
loadDataPiPiHome
();
await
_headerHomeVM
.
freshData
();
}
...
...
lib/screen/home/home_tab_viewmodel.dart
View file @
a0bcdab2
...
...
@@ -40,7 +40,7 @@ class HomeTabViewModel extends RestfulApiViewModel {
return
sectionLayouts
.
firstWhereOrNull
((
section
)
=>
section
.
headerSectionType
==
type
);
}
Future
<
void
>
getSectionLayoutHome
()
async
{
Future
<
void
>
getSectionLayoutHome
(
{
bool
showLoading
=
true
}
)
async
{
await
callApi
<
List
<
MainSectionConfigModel
>>(
request:
()
=>
client
.
getSectionLayoutHome
(),
onSuccess:
(
data
,
_
)
{
...
...
@@ -57,6 +57,7 @@ class HomeTabViewModel extends RestfulApiViewModel {
await
_processSection
(
section
);
}
},
withLoading:
showLoading
,
);
}
...
...
lib/screen/login/login_viewmodel.dart
View file @
a0bcdab2
...
...
@@ -89,11 +89,11 @@ class LoginViewModel extends RestfulApiViewModel {
}
}
void
onChangePhonePressed
()
{
Future
<
void
>
onChangePhonePressed
()
async
{
if
(
Get
.
key
.
currentState
?.
canPop
()
==
true
)
{
Get
.
back
();
}
else
{
DataPreference
.
instance
.
clearData
();
await
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
onboardingScreen
);
}
}
...
...
lib/screen/otp/delete_account_otp_repository.dart
View file @
a0bcdab2
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
...
...
@@ -23,29 +22,34 @@ class DeleteAccountOtpRepository extends RestfulApiViewModel implements IOtpRepo
@override
Future
<
BaseResponseModel
<
EmptyCodable
>>
verifyOtp
(
String
otpCode
)
async
{
showLoading
();
return
client
.
verifyDeleteAccount
(
otpCode
).
then
((
value
)
{
hideLoading
(
);
try
{
final
value
=
await
client
.
verifyDeleteAccount
(
otpCode
);
if
(
value
.
isSuccess
)
{
DataPreference
.
instance
.
clearBioToken
(
phoneNumber
);
DataPreference
.
instance
.
clearData
();
await
DataPreference
.
instance
.
clearBioToken
(
phoneNumber
);
await
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
onboardingScreen
);
showToastMessage
(
"Xóa tài khoản thành công"
);
}
return
value
;
});
}
finally
{
hideLoading
();
}
}
@override
Future
<
int
?>
resendOtp
()
async
{
showLoading
();
client
.
requestOtpDeleteAccount
().
then
((
value
)
{
hideLoading
();
try
{
final
value
=
await
client
.
requestOtpDeleteAccount
();
if
(
value
.
isSuccess
)
{
otpTtl
=
value
.
data
?.
resendAfterSecond
??
Constants
.
otpTtl
;
}
else
{
final
mgs
=
value
.
errorMessage
??
Constants
.
commonError
;
Get
.
snackbar
(
"Thông báo"
,
mgs
);
return
otpTtl
;
}
});
final
mgs
=
value
.
errorMessage
??
Constants
.
commonError
;
Get
.
snackbar
(
"Thông báo"
,
mgs
);
return
null
;
}
finally
{
hideLoading
();
}
}
}
lib/screen/personal/personal_screen.dart
View file @
a0bcdab2
...
...
@@ -12,6 +12,7 @@ import '../../preference/point/header_home_model.dart';
import
'../../resources/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../services/logout_service.dart'
;
import
'package:mypoint_flutter_app/web/web_helper.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'../popup_manager/popup_runner_helper.dart'
;
...
...
@@ -31,7 +32,6 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
void
initState
()
{
super
.
initState
();
_loadAppInfo
();
_headerHomeVM
.
freshData
();
runPopupCheck
(
DirectionalScreenName
.
personal
);
}
...
...
@@ -317,9 +317,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
AlertButton
(
text:
"Đồng ý"
,
onPressed:
()
async
{
LogoutService
.
logout
();
DataPreference
.
instance
.
clearLoginToken
();
_safeBackToLogin
();
await
_handleLogout
();
},
bgColor:
BaseColor
.
primary500
,
textColor:
Colors
.
white
,
...
...
@@ -330,14 +328,30 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
showAlert
(
data:
dataAlert
);
}
void
_
safeBackToLogin
()
{
Future
<
void
>
_
handleLogout
()
async
{
final
phone
=
DataPreference
.
instance
.
phoneNumberUsedForLoginScreen
;
final
displayName
=
DataPreference
.
instance
.
displayName
;
await
LogoutService
.
logout
();
if
(
kIsWeb
)
{
await
DataPreference
.
instance
.
clearData
();
final
closed
=
await
webCloseApp
({
'message'
:
'User logged out successfully'
,
'timestamp'
:
DateTime
.
now
().
millisecondsSinceEpoch
,
});
if
(!
closed
)
{
Get
.
offAllNamed
(
onboardingScreen
);
}
return
;
}
print
(
"Safe back to login screen with phone:
$phone
, displayName:
$displayName
"
);
if
(
phone
!=
null
)
{
if
(
phone
.
isNotEmpty
)
{
await
DataPreference
.
instance
.
clearLoginToken
();
Get
.
offAllNamed
(
loginScreen
,
arguments:
{
"phone"
:
phone
,
'fullName'
:
displayName
});
}
else
{
DataPreference
.
instance
.
clearData
();
await
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
onboardingScreen
);
}
}
...
...
lib/screen/support/support_screen.dart
View file @
a0bcdab2
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/support/support_item_model.dart'
;
import
'package:mypoint_flutter_app/screen/support/support_screen_viewmodel.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../widgets/back_button.dart'
;
import
'package:universal_html/js_util.dart'
as
js_util
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../faqs/faqs_screen.dart'
;
import
'../pageDetail/campaign_detail_screen.dart'
;
import
'../pageDetail/model/detail_page_rule_type.dart'
;
import
'../../web/web_helper.dart'
;
class
SupportScreen
extends
StatefulWidget
{
const
SupportScreen
({
super
.
key
});
...
...
@@ -30,11 +30,29 @@ class _SupportScreenState extends State<SupportScreen> {
}
break
;
case
SupportItemType
.
phone
:
final
Uri
phoneUri
=
Uri
(
scheme:
'tel'
,
path:
value
);
if
(
await
canLaunchUrl
(
phoneUri
))
{
await
launchUrl
(
phoneUri
);
final
phone
=
value
.
trim
();
if
(
phone
.
isEmpty
)
{
if
(
kDebugMode
)
{
print
(
'⚠️ SupportScreen: phone number is empty'
);
}
return
;
}
break
;
if
(
kIsWeb
)
{
try
{
final
result
=
await
webCallPhone
(
phone
);
if
(!
_isSdkSuccess
(
result
))
{
await
_launchTelUrl
(
phone
);
}
}
catch
(
e
)
{
if
(
kDebugMode
)
{
print
(
'❌ webCallPhone failed:
$e
'
);
}
await
_launchTelUrl
(
phone
);
}
}
else
{
await
_launchTelUrl
(
phone
);
}
return
;
case
SupportItemType
.
facebook
:
final
Uri
fbUri
=
Uri
.
parse
(
value
);
if
(
await
canLaunchUrl
(
fbUri
))
{
...
...
@@ -43,10 +61,13 @@ class _SupportScreenState extends State<SupportScreen> {
break
;
case
SupportItemType
.
question
:
Get
.
to
(
FAQScreen
());
return
;
case
SupportItemType
.
termsOfUse
:
Get
.
toNamed
(
campaignDetailScreen
,
arguments:
{
"type"
:
DetailPageRuleType
.
termsOfUse
});
return
;
case
SupportItemType
.
privacyPolicy
:
Get
.
toNamed
(
campaignDetailScreen
,
arguments:
{
"type"
:
DetailPageRuleType
.
privacyPolicy
});
return
;
}
}
...
...
@@ -116,4 +137,28 @@ class _SupportScreenState extends State<SupportScreen> {
type
==
SupportItemType
.
termsOfUse
||
type
==
SupportItemType
.
privacyPolicy
;
}
Future
<
void
>
_launchTelUrl
(
String
phone
)
async
{
final
Uri
phoneUri
=
Uri
(
scheme:
'tel'
,
path:
phone
);
if
(
await
canLaunchUrl
(
phoneUri
))
{
await
launchUrl
(
phoneUri
);
}
}
bool
_isSdkSuccess
(
dynamic
result
)
{
try
{
if
(
result
==
null
)
return
false
;
if
(
result
is
Map
)
{
final
status
=
result
[
'status'
]?.
toString
().
toLowerCase
();
return
status
==
'success'
;
}
if
(
js_util
.
hasProperty
(
result
,
'status'
))
{
final
status
=
js_util
.
getProperty
(
result
,
'status'
);
if
(
status
is
String
)
{
return
status
.
toLowerCase
()
==
'success'
;
}
}
}
catch
(
_
)
{}
return
false
;
}
}
lib/screen/voucher/voucher_list/voucher_list_viewmodel.dart
View file @
a0bcdab2
...
...
@@ -3,7 +3,6 @@ import 'package:get/get.dart';
import
'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'
;
import
'../../../base/base_response_model.dart'
;
import
'../../../networking/restful_api_viewmodel.dart'
;
import
'../../../widgets/alert/popup_data_model.dart'
;
import
'../models/product_model.dart'
;
import
'../models/product_type.dart'
;
...
...
lib/screen/voucher/voucher_tab_screen.dart
View file @
a0bcdab2
...
...
@@ -22,15 +22,17 @@ class VoucherTabScreen extends StatefulWidget {
}
class
_VoucherTabScreenState
extends
State
<
VoucherTabScreen
>
with
PopupOnInit
{
final
VoucherTabViewModel
viewModel
=
Get
.
put
(
VoucherTabViewModel
());
@override
void
initState
()
{
super
.
initState
();
runPopupCheck
(
DirectionalScreenName
.
productVoucher
);
viewModel
.
refreshData
();
}
@override
Widget
build
(
BuildContext
context
)
{
final
VoucherTabViewModel
viewModel
=
Get
.
put
(
VoucherTabViewModel
());
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Ưu đãi"
,
...
...
@@ -45,11 +47,10 @@ class _VoucherTabScreenState extends State<VoucherTabScreen> with PopupOnInit {
],
),
body:
Obx
(()
{
if
(
viewModel
.
isLoading
.
value
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
return
RefreshIndicator
(
onRefresh:
viewModel
.
refreshData
,
onRefresh:
()
async
{
await
viewModel
.
refreshData
(
isShowLoading:
false
);
},
child:
NotificationListener
<
ScrollNotification
>(
onNotification:
(
ScrollNotification
scrollInfo
)
{
if
(
scrollInfo
is
ScrollUpdateNotification
&&
...
...
@@ -76,7 +77,7 @@ class _VoucherTabScreenState extends State<VoucherTabScreen> with PopupOnInit {
VoucherItemGrid
(
items:
viewModel
.
hotProducts
),
const
HeaderSectionTitle
(
title:
'Tất cả ưu đãi'
),
VoucherItemList
(
items:
viewModel
.
allProducts
),
if
(
viewModel
.
isLoadMore
.
value
)
if
(
viewModel
.
isLoadMore
.
value
&&
viewModel
.
allProducts
.
isNotEmpty
)
const
Padding
(
padding:
EdgeInsets
.
symmetric
(
vertical:
16
),
child:
Center
(
child:
CircularProgressIndicator
()),
...
...
lib/screen/voucher/voucher_tab_viewmodel.dart
View file @
a0bcdab2
...
...
@@ -8,28 +8,25 @@ import 'models/product_model.dart';
class
VoucherTabViewModel
extends
RestfulApiViewModel
{
final
RxList
<
ProductModel
>
hotProducts
=
<
ProductModel
>[].
obs
;
final
RxList
<
ProductModel
>
allProducts
=
<
ProductModel
>[].
obs
;
final
RxBool
isLoading
=
false
.
obs
;
final
RxBool
isLoadMore
=
false
.
obs
;
bool
get
_isDataAvailable
{
return
hotProducts
.
isNotEmpty
||
allProducts
.
isNotEmpty
;
}
int
_currentPage
=
0
;
final
int
_pageSize
=
20
;
bool
_hasMore
=
true
;
bool
get
hasMore
=>
_hasMore
;
@override
void
onInit
()
{
super
.
onInit
();
refreshData
();
}
Future
<
void
>
refreshData
()
async
{
isLoading
.
value
=
true
;
Future
<
void
>
refreshData
({
bool
isShowLoading
=
true
})
async
{
if
(
isShowLoading
&&
_isDataAvailable
)
return
;
if
(
isShowLoading
)
showLoading
();
await
Future
.
wait
([
getHotProducts
(),
getAllProducts
(
reset:
true
),
]);
i
sLoading
.
value
=
false
;
i
f
(
isShowLoading
)
hideLoading
()
;
}
Future
<
void
>
getHotProducts
()
async
{
...
...
@@ -51,7 +48,6 @@ class VoucherTabViewModel extends RestfulApiViewModel {
if
(
reset
)
{
_currentPage
=
0
;
_hasMore
=
true
;
allProducts
.
clear
();
}
else
{
_currentPage
=
allProducts
.
length
;
}
...
...
@@ -71,7 +67,11 @@ class VoucherTabViewModel extends RestfulApiViewModel {
if
(
fetchedData
.
isEmpty
||
fetchedData
.
length
<
_pageSize
)
{
_hasMore
=
false
;
}
allProducts
.
addAll
(
fetchedData
);
if
(
reset
)
{
allProducts
.
value
=
fetchedData
;
}
else
{
allProducts
.
addAll
(
fetchedData
);
}
}
catch
(
error
)
{
print
(
"Error fetching all products:
$error
"
);
}
finally
{
...
...
lib/services/token_refresh_service.dart
View file @
a0bcdab2
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'
;
...
...
@@ -22,7 +21,7 @@ class TokenRefreshService extends RestfulApiViewModel {
final
token
=
DataPreference
.
instance
.
token
;
final
refreshToken
=
DataPreference
.
instance
.
refreshToken
;
if
(
token
.
orEmpty
.
isEmpty
||
refreshToken
.
orEmpty
.
isEmpty
)
{
_handleRefreshFailure
();
await
_handleRefreshFailure
();
return
;
}
_isRefreshing
=
true
;
...
...
@@ -34,11 +33,11 @@ class TokenRefreshService extends RestfulApiViewModel {
await
_getUserProfileAfterRefresh
();
_handleRefreshSuccess
();
}
else
{
_handleRefreshFailure
();
await
_handleRefreshFailure
();
}
}
catch
(
e
)
{
print
(
'Token refresh error:
$e
'
);
_handleRefreshFailure
();
await
_handleRefreshFailure
();
}
finally
{
_isRefreshing
=
false
;
}
...
...
@@ -65,8 +64,8 @@ class TokenRefreshService extends RestfulApiViewModel {
}
/// Xử lý refresh thất bại - redirect về login
void
_handleRefreshFailure
()
{
DataPreference
.
instance
.
clearData
();
Future
<
void
>
_handleRefreshFailure
()
async
{
await
DataPreference
.
instance
.
clearData
();
for
(
final
callback
in
_callbacks
)
{
callback
(
false
);
}
...
...
lib/web/web_helper_stub.dart
View file @
a0bcdab2
// Stub implementations for non-web platforms
import
'package:flutter/foundation.dart'
;
/// Initialize x-app-sdk service (no-op on non-web)
Future
<
void
>
webInitializeXAppSDK
()
async
{
...
...
@@ -11,8 +12,8 @@ Future<String?> webGetToken() async {
}
/// 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
Future
<
bool
>
webCloseApp
([
Map
<
String
,
dynamic
>?
data
])
async
{
return
false
;
}
/// Check if x-app-sdk is initialized (no-op on non-web)
...
...
@@ -39,3 +40,83 @@ void webClearTokenCache() {
void
webResetSDK
(
)
{
// no-op on non-web
}
Future
<
dynamic
>
webConfigUIApp
(
Map
<
String
,
dynamic
>
config
)
async
{
return
null
;
}
Future
<
dynamic
>
webCallPhone
(
String
phoneNumber
)
async
{
return
null
;
}
Future
<
dynamic
>
webCall
(
String
phoneNumber
)
async
{
return
null
;
}
Future
<
dynamic
>
webSendSms
(
String
phoneNumber
)
async
{
return
null
;
}
Future
<
dynamic
>
webSms
(
String
phoneNumber
)
async
{
return
null
;
}
Future
<
dynamic
>
webVibrate
()
async
{
return
null
;
}
Future
<
dynamic
>
webCurrentLocation
()
async
{
return
null
;
}
Future
<
dynamic
>
webRequestLocationPermission
()
async
{
return
null
;
}
Future
<
dynamic
>
webOpenPickerImage
(
dynamic
type
)
async
{
return
null
;
}
Future
<
dynamic
>
webOpenPickerFile
([
dynamic
options
])
async
{
return
null
;
}
Future
<
dynamic
>
webPaymentRequest
(
Map
<
String
,
dynamic
>
payload
)
async
{
return
null
;
}
Future
<
VoidCallback
?>
webListenNotificationEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
return
null
;
}
Future
<
VoidCallback
?>
webListenPaymentEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
return
null
;
}
Future
<
dynamic
>
webPermissionsRequest
(
dynamic
type
)
async
{
return
null
;
}
Future
<
dynamic
>
webPremissionsRequest
(
dynamic
type
)
async
{
return
null
;
}
Future
<
dynamic
>
webSaveStore
(
dynamic
data
)
async
{
return
null
;
}
Future
<
dynamic
>
webGetStore
()
async
{
return
null
;
}
Future
<
dynamic
>
webClearStore
()
async
{
return
null
;
}
Future
<
dynamic
>
webGetInfo
(
dynamic
key
)
async
{
return
null
;
}
lib/web/web_helper_web.dart
View file @
a0bcdab2
// 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
'package:flutter/foundation.dart'
;
import
'x_app_sdk_service.dart'
;
/// Web-specific helper functions for x-app-sdk integration
...
...
@@ -26,11 +24,12 @@ Future<String?> webGetToken() async {
}
/// Close app and return to Super App
Future
<
void
>
webCloseApp
([
Map
<
String
,
dynamic
>?
data
])
async
{
Future
<
bool
>
webCloseApp
([
Map
<
String
,
dynamic
>?
data
])
async
{
try
{
await
XAppSDKService
().
closeApp
(
data
);
return
await
XAppSDKService
().
closeApp
(
data
);
}
catch
(
e
)
{
print
(
'❌ Error closing app:
$e
'
);
return
false
;
}
}
...
...
@@ -81,3 +80,158 @@ void webResetSDK() {
print
(
'❌ Error resetting SDK:
$e
'
);
}
}
Future
<
dynamic
>
webConfigUIApp
(
Map
<
String
,
dynamic
>
config
)
async
{
try
{
return
await
XAppSDKService
().
configUIApp
(
config
);
}
catch
(
e
)
{
print
(
'❌ Error configuring UI app:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webCallPhone
(
String
phoneNumber
)
async
{
try
{
return
await
XAppSDKService
().
callPhone
(
phoneNumber
);
}
catch
(
e
)
{
print
(
'❌ Error calling phone:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webCall
(
String
phoneNumber
)
=>
webCallPhone
(
phoneNumber
);
Future
<
dynamic
>
webSendSms
(
String
phoneNumber
)
async
{
try
{
return
await
XAppSDKService
().
sendSms
(
phoneNumber
);
}
catch
(
e
)
{
print
(
'❌ Error sending SMS:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webSms
(
String
phoneNumber
)
=>
webSendSms
(
phoneNumber
);
Future
<
dynamic
>
webVibrate
()
async
{
try
{
return
await
XAppSDKService
().
vibrate
();
}
catch
(
e
)
{
print
(
'❌ Error vibrating device:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webCurrentLocation
()
async
{
try
{
return
await
XAppSDKService
().
currentLocation
();
}
catch
(
e
)
{
print
(
'❌ Error getting current location:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webRequestLocationPermission
()
async
{
try
{
return
await
XAppSDKService
().
requestLocationPermission
();
}
catch
(
e
)
{
print
(
'❌ Error requesting location permission:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webOpenPickerImage
(
dynamic
type
)
async
{
try
{
return
await
XAppSDKService
().
openPickerImage
(
type
);
}
catch
(
e
)
{
print
(
'❌ Error opening image picker:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webOpenPickerFile
([
dynamic
options
])
async
{
try
{
return
await
XAppSDKService
().
openPickerFile
(
options
);
}
catch
(
e
)
{
print
(
'❌ Error opening file picker:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webPaymentRequest
(
Map
<
String
,
dynamic
>
payload
)
async
{
try
{
return
await
XAppSDKService
().
paymentRequest
(
payload
);
}
catch
(
e
)
{
print
(
'❌ Error creating payment request:
$e
'
);
return
null
;
}
}
Future
<
VoidCallback
?>
webListenNotificationEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
try
{
return
await
XAppSDKService
().
listenNotificationEvent
(
onEvent
);
}
catch
(
e
)
{
print
(
'❌ Error registering notification listener:
$e
'
);
return
null
;
}
}
Future
<
VoidCallback
?>
webListenPaymentEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
try
{
return
await
XAppSDKService
().
listenPaymentEvent
(
onEvent
);
}
catch
(
e
)
{
print
(
'❌ Error registering payment listener:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webPermissionsRequest
(
dynamic
type
)
async
{
try
{
return
await
XAppSDKService
().
permissionsRequest
(
type
);
}
catch
(
e
)
{
print
(
'❌ Error requesting permission:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webPremissionsRequest
(
dynamic
type
)
=>
webPermissionsRequest
(
type
);
Future
<
dynamic
>
webSaveStore
(
dynamic
data
)
async
{
try
{
return
await
XAppSDKService
().
saveStore
(
data
);
}
catch
(
e
)
{
print
(
'❌ Error saving store data:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webGetStore
()
async
{
try
{
return
await
XAppSDKService
().
getStore
();
}
catch
(
e
)
{
print
(
'❌ Error getting store data:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webClearStore
()
async
{
try
{
return
await
XAppSDKService
().
clearStore
();
}
catch
(
e
)
{
print
(
'❌ Error clearing store:
$e
'
);
return
null
;
}
}
Future
<
dynamic
>
webGetInfo
(
dynamic
key
)
async
{
try
{
return
await
XAppSDKService
().
getInfo
(
key
);
}
catch
(
e
)
{
print
(
'❌ Error getting info:
$e
'
);
return
null
;
}
}
lib/web/x_app_sdk_service.dart
View file @
a0bcdab2
...
...
@@ -3,7 +3,7 @@ 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
/// Provides integration with x-app-sdk
module and exposes supported APIs to Dart
class
XAppSDKService
{
static
final
XAppSDKService
_instance
=
XAppSDKService
.
_internal
();
factory
XAppSDKService
()
=>
_instance
;
...
...
@@ -13,6 +13,8 @@ class XAppSDKService {
dynamic
_sdkModule
;
String
?
_cachedToken
;
String
?
_lastError
;
final
Set
<
dynamic
>
_listenerDisposers
=
<
dynamic
>{};
bool
_browserMode
=
false
;
/// Check if SDK is available and initialized
bool
get
isInitialized
=>
_isInitialized
;
...
...
@@ -26,7 +28,10 @@ class XAppSDKService {
/// Initialize the SDK service
Future
<
void
>
initialize
()
async
{
if
(!
kIsWeb
)
{
print
(
'⚠️ XAppSDKService: Not running on web platform'
);
print
(
'⚠️ XAppSDKService: initialize() called on non-web platform'
);
return
;
}
if
(
_isInitialized
&&
_sdkModule
!=
null
)
{
return
;
}
...
...
@@ -40,8 +45,8 @@ class XAppSDKService {
}
_sdkModule
=
module
;
_isInitialized
=
true
;
_lastError
=
null
;
print
(
'✅ XAppSDKService: Initialized successfully'
);
}
catch
(
e
)
{
_lastError
=
'Failed to initialize SDK:
$e
'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
...
...
@@ -50,40 +55,17 @@ class XAppSDKService {
/// Get token from x-app-sdk
Future
<
String
?>
getToken
()
async
{
if
(!
kIsWeb
)
{
print
(
'⚠️ XAppSDKService: getToken() called on non-web platform'
);
if
(!
await
_ensureSdkReady
())
{
return
null
;
}
if
(!
_isInitialized
)
{
print
(
'⚠️ XAppSDKService: SDK not initialized, attempting to initialize...'
);
await
initialize
();
if
(!
_isInitialized
)
{
_lastError
=
'SDK initialization failed'
;
return
null
;
}
}
print
(
'🔍 XAppSDKService: Getting token...'
);
try
{
print
(
'🔍 XAppSDKService: Getting token...'
);
final
sdk
=
await
_loadSdkModule
();
if
(
sdk
==
null
)
{
_lastError
=
'x-app-sdk not available'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
final
hasGetToken
=
_hasProperty
(
sdk
,
'getToken'
);
if
(!
hasGetToken
)
{
_lastError
=
'getToken method not found in SDK'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
final
result
=
await
_invokeSdkMethod
(
'getToken'
,
ensureInitialized:
false
,
);
// 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'
);
...
...
@@ -94,19 +76,21 @@ class XAppSDKService {
final
tokenString
=
data
.
toString
();
_cachedToken
=
tokenString
;
_lastError
=
null
;
final
preview
=
tokenString
.
length
>
8
?
tokenString
.
substring
(
0
,
8
)
:
tokenString
;
print
(
'✅ XAppSDKService: Token retrieved successfully:
$preview
...'
);
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
'
:
''
;
final
details
=
errorMessage
?.
isNotEmpty
==
true
?
' -
$errorMessage
'
:
''
;
_lastError
=
'SDK returned status:
$status$details
'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
}
}
else
{
_lastError
=
'getToken returned null'
;
_lastError
??
=
'getToken returned null'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
}
}
catch
(
e
)
{
_lastError
=
'Error getting token:
$e
'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
...
...
@@ -116,61 +100,143 @@ class XAppSDKService {
}
/// 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
;
Future
<
bool
>
closeApp
([
Map
<
String
,
dynamic
>?
data
])
async
{
if
(!
await
_ensureSdkReady
())
{
print
(
'❌ XAppSDKService: closeApp skipped - SDK not ready'
);
_fallbackClose
();
return
false
;
}
if
(!
_isInitialized
)
{
print
(
'⚠️ XAppSDKService: SDK not initialized, attempting to initialize...'
);
await
initialize
();
if
(!
_isInitialized
)
{
print
(
'❌ XAppSDKService: Cannot close app - SDK not initialized'
);
return
;
}
if
(
_browserMode
)
{
print
(
'ℹ️ XAppSDKService: Running in browser mode, falling back from closeApp'
);
_fallbackClose
();
return
false
;
}
try
{
print
(
'🔍 XAppSDKService: Closing app...'
);
final
sdk
=
await
_loadSdkModule
();
if
(
sdk
==
null
)
{
print
(
'❌ XAppSDKService: x-app-sdk not available for closeApp'
);
return
;
}
print
(
'🔍 XAppSDKService: Closing app...'
);
final
result
=
await
_invokeSdkMethod
(
'closeApp'
,
args:
data
!=
null
?
<
dynamic
>[
data
]
:
const
<
dynamic
>[],
expectPromise:
false
,
ensureInitialized:
false
,
);
final
hasCloseApp
=
_hasProperty
(
sdk
,
'closeApp'
);
if
(!
hasCloseApp
)
{
print
(
'❌ XAppSDKService: closeApp method not found in SDK'
);
return
;
}
// Execute closeApp method from SDK with optional data
final
success
=
_lastError
==
null
&&
_isSuccessResult
(
result
);
if
(
success
)
{
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
{
if
(
html
.
window
.
history
.
length
>
1
)
{
html
.
window
.
history
.
back
();
}
else
{
html
.
window
.
close
();
}
print
(
'✅ XAppSDKService: Fallback close executed'
);
}
catch
(
fallbackError
)
{
print
(
'❌ XAppSDKService: Fallback close also failed:
$fallbackError
'
);
}
return
true
;
}
final
reason
=
_lastError
??
'closeApp returned non-success result'
;
print
(
'❌ XAppSDKService:
$reason
'
);
_fallbackClose
();
return
false
;
}
/// Config UI inside Super App
Future
<
dynamic
>
configUIApp
(
Map
<
String
,
dynamic
>
config
)
=>
_invokeAfterEnsure
(
'configUIApp'
,
args:
<
dynamic
>[
config
]);
/// Trigger a phone call via Super App
Future
<
dynamic
>
callPhone
(
String
phoneNumber
)
=>
_invokeAfterEnsure
(
'call'
,
args:
<
dynamic
>[
phoneNumber
]);
/// Trigger sending SMS via Super App
Future
<
dynamic
>
sendSms
(
String
phoneNumber
)
=>
_invokeAfterEnsure
(
'sms'
,
args:
<
dynamic
>[
phoneNumber
]);
/// Vibrate the device
Future
<
dynamic
>
vibrate
()
=>
_invokeAfterEnsure
(
'vibrate'
);
/// Get current device location
Future
<
dynamic
>
currentLocation
()
=>
_invokeAfterEnsure
(
'currentLocation'
);
/// Request location permission
Future
<
dynamic
>
requestLocationPermission
()
=>
_invokeAfterEnsure
(
'requestLocationPermission'
);
/// Open image picker
Future
<
dynamic
>
openPickerImage
(
dynamic
type
)
=>
_invokeAfterEnsure
(
'openPickerImage'
,
args:
<
dynamic
>[
type
]);
/// Open file picker
Future
<
dynamic
>
openPickerFile
([
dynamic
options
])
=>
options
==
null
?
_invokeAfterEnsure
(
'openPickerFile'
)
:
_invokeAfterEnsure
(
'openPickerFile'
,
args:
<
dynamic
>[
options
]);
/// Send payment request
Future
<
dynamic
>
paymentRequest
(
Map
<
String
,
dynamic
>
payload
)
=>
_invokeAfterEnsure
(
'paymentRequest'
,
args:
<
dynamic
>[
payload
]);
/// Listen to notification event
Future
<
VoidCallback
?>
listenNotificationEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
final
disposer
=
await
_invokeAfterEnsure
(
'listenNotifiactionEvent'
,
args:
<
dynamic
>[
allowInterop
((
dynamic
event
)
{
try
{
onEvent
(
event
);
}
catch
(
error
,
stackTrace
)
{
print
(
'❌ XAppSDKService: Error in notification listener:
$error
'
);
debugPrintStack
(
stackTrace:
stackTrace
);
}
}),
],
expectPromise:
false
,
);
return
_registerListenerDisposer
(
'listenNotifiactionEvent'
,
disposer
);
}
/// Listen to payment events
Future
<
VoidCallback
?>
listenPaymentEvent
(
ValueChanged
<
dynamic
>
onEvent
,
)
async
{
final
disposer
=
await
_invokeAfterEnsure
(
'listenPaymentEvent'
,
args:
<
dynamic
>[
allowInterop
((
dynamic
event
)
{
try
{
onEvent
(
event
);
}
catch
(
error
,
stackTrace
)
{
print
(
'❌ XAppSDKService: Error in payment listener:
$error
'
);
debugPrintStack
(
stackTrace:
stackTrace
);
}
}),
],
expectPromise:
false
,
);
return
_registerListenerDisposer
(
'listenPaymentEvent'
,
disposer
);
}
/// Request permissions (SDK uses misspelled 'premissionsRequest')
Future
<
dynamic
>
permissionsRequest
(
dynamic
type
)
=>
_invokeAfterEnsure
(
'premissionsRequest'
,
args:
<
dynamic
>[
type
]);
/// Alias for the SDK method name
Future
<
dynamic
>
premissionsRequest
(
dynamic
type
)
=>
permissionsRequest
(
type
);
/// Persist data into Super App store
Future
<
dynamic
>
saveStore
(
dynamic
data
)
=>
_invokeAfterEnsure
(
'saveStore'
,
args:
<
dynamic
>[
data
]);
/// Retrieve data from Super App store
Future
<
dynamic
>
getStore
()
=>
_invokeAfterEnsure
(
'getStore'
);
/// Clear Super App store data
Future
<
dynamic
>
clearStore
()
=>
_invokeAfterEnsure
(
'clearStore'
);
/// Get user info
Future
<
dynamic
>
getInfo
(
dynamic
key
)
=>
_invokeAfterEnsure
(
'getInfo'
,
args:
<
dynamic
>[
key
]);
/// Clear cached token
void
clearToken
()
{
_cachedToken
=
null
;
...
...
@@ -180,23 +246,218 @@ class XAppSDKService {
/// Reset service state
void
reset
()
{
_disposeAllListeners
();
_isInitialized
=
false
;
_sdkModule
=
null
;
_cachedToken
=
null
;
_lastError
=
null
;
_browserMode
=
false
;
try
{
final
loader
=
getProperty
(
html
.
window
,
'__xAppSdkLoader'
);
if
(
loader
!=
null
)
{
if
(
_hasProperty
(
loader
,
'resetCachedModule'
))
{
callMethod
(
loader
,
'resetCachedModule'
,
[]);
}
if
(
loader
!=
null
&&
_hasProperty
(
loader
,
'resetCachedModule'
))
{
callMethod
(
loader
,
'resetCachedModule'
,
<
dynamic
>[]);
}
}
catch
(
_
)
{}
print
(
'🔄 XAppSDKService: Service reset'
);
}
Future
<
bool
>
_ensureSdkReady
()
async
{
if
(!
kIsWeb
)
{
print
(
'⚠️ XAppSDKService: SDK requested on non-web platform'
);
return
false
;
}
if
(
_isInitialized
&&
_sdkModule
!=
null
)
{
return
true
;
}
print
(
'⚠️ XAppSDKService: SDK not initialized, attempting to initialize...'
);
await
initialize
();
return
_isInitialized
&&
_sdkModule
!=
null
;
}
Future
<
dynamic
>
_invokeAfterEnsure
(
String
methodName
,
{
List
<
dynamic
>
args
=
const
<
dynamic
>[],
bool
expectPromise
=
true
,
})
async
{
if
(!
await
_ensureSdkReady
())
{
return
null
;
}
return
_invokeSdkMethod
(
methodName
,
args:
args
,
expectPromise:
expectPromise
,
ensureInitialized:
false
,
);
}
Future
<
dynamic
>
_invokeSdkMethod
(
String
methodName
,
{
List
<
dynamic
>
args
=
const
<
dynamic
>[],
bool
expectPromise
=
true
,
bool
ensureInitialized
=
true
,
})
async
{
if
(
ensureInitialized
&&
!
await
_ensureSdkReady
())
{
return
null
;
}
final
sdk
=
await
_loadSdkModule
();
if
(
sdk
==
null
)
{
_lastError
=
'x-app-sdk not available'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
if
(!
_hasProperty
(
sdk
,
methodName
))
{
_lastError
=
'
$methodName
method not found in SDK'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
try
{
final
preparedArgs
=
args
.
isEmpty
?
<
dynamic
>[]
:
List
<
dynamic
>.
from
(
args
.
map
(
_prepareArgument
));
final
result
=
callMethod
(
sdk
,
methodName
,
preparedArgs
);
if
(
expectPromise
)
{
try
{
final
awaited
=
await
promiseToFuture
(
result
);
_lastError
=
null
;
return
awaited
;
}
catch
(
_
)
{
// Not a promise, fall through and return raw value.
}
}
_lastError
=
null
;
return
result
;
}
catch
(
e
)
{
_lastError
=
'Error invoking
$methodName
:
$e
'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
}
dynamic
_prepareArgument
(
dynamic
value
)
{
if
(
value
==
null
)
{
return
null
;
}
if
(
value
is
Map
||
value
is
Iterable
)
{
try
{
return
jsify
(
value
);
}
catch
(
_
)
{
// Fall through and return value as-is if jsify fails.
}
}
return
value
;
}
VoidCallback
?
_registerListenerDisposer
(
String
methodName
,
dynamic
disposer
,
)
{
if
(
disposer
==
null
)
{
if
(
_lastError
!=
null
)
{
print
(
'❌ XAppSDKService: Failed to register
$methodName
listener -
$_lastError
'
);
}
else
{
print
(
'⚠️ XAppSDKService:
$methodName
did not return a disposer function'
);
}
return
null
;
}
_listenerDisposers
.
add
(
disposer
);
print
(
'🔔 XAppSDKService:
$methodName
listener registered'
);
return
()
{
_invokeJsFunction
(
disposer
);
_listenerDisposers
.
remove
(
disposer
);
};
}
void
_invokeJsFunction
(
dynamic
fn
)
{
if
(
fn
==
null
)
{
return
;
}
try
{
final
result
=
callMethod
(
fn
,
'call'
,
<
dynamic
>[
null
]);
try
{
promiseToFuture
(
result
);
}
catch
(
_
)
{
// Ignore non-Promise results.
}
}
catch
(
e
)
{
print
(
'❌ XAppSDKService: Failed to invoke JS callback:
$e
'
);
}
}
void
_disposeAllListeners
()
{
if
(
_listenerDisposers
.
isEmpty
)
{
return
;
}
final
disposers
=
List
<
dynamic
>.
from
(
_listenerDisposers
);
for
(
final
disposer
in
disposers
)
{
_invokeJsFunction
(
disposer
);
}
_listenerDisposers
.
clear
();
}
void
_fallbackClose
()
{
try
{
if
(
html
.
window
.
history
.
length
>
1
)
{
html
.
window
.
history
.
back
();
}
else
{
html
.
window
.
close
();
}
print
(
'✅ XAppSDKService: Fallback close executed'
);
}
catch
(
fallbackError
)
{
print
(
'❌ XAppSDKService: Fallback close failed:
$fallbackError
'
);
}
}
bool
_isSuccessResult
(
dynamic
result
)
{
if
(
result
==
null
)
{
return
true
;
}
if
(
result
is
bool
)
{
return
result
;
}
if
(
result
is
Map
)
{
final
status
=
result
[
'status'
]?.
toString
().
toLowerCase
();
if
(
status
!=
null
)
{
return
status
==
'success'
;
}
if
(
result
.
containsKey
(
'success'
))
{
final
successValue
=
result
[
'success'
];
if
(
successValue
is
bool
)
{
return
successValue
;
}
return
successValue
?.
toString
().
toLowerCase
()
==
'true'
;
}
}
try
{
if
(
_hasProperty
(
result
,
'status'
))
{
final
status
=
getProperty
(
result
,
'status'
);
if
(
status
is
String
)
{
return
status
.
toLowerCase
()
==
'success'
;
}
}
if
(
_hasProperty
(
result
,
'success'
))
{
final
successValue
=
getProperty
(
result
,
'success'
);
if
(
successValue
is
bool
)
{
return
successValue
;
}
return
successValue
?.
toString
().
toLowerCase
()
==
'true'
;
}
}
catch
(
_
)
{}
return
true
;
}
Future
<
dynamic
>
_loadSdkModule
()
async
{
if
(
_sdkModule
!=
null
)
{
_browserMode
=
_detectBrowserMode
(
_sdkModule
);
return
_sdkModule
;
}
...
...
@@ -216,7 +477,9 @@ class XAppSDKService {
}
try
{
final
module
=
await
promiseToFuture
(
callMethod
(
loader
,
'loadXAppSdkModule'
,
[]));
final
module
=
await
promiseToFuture
(
callMethod
(
loader
,
'loadXAppSdkModule'
,
<
dynamic
>[]),
);
if
(
module
==
null
)
{
_lastError
=
'x-app-sdk module resolved to null'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
...
...
@@ -228,15 +491,15 @@ class XAppSDKService {
print
(
'🔗 XAppSDKService: Module loaded from
$source
'
);
}
final
hasGetToken
=
_hasProperty
(
module
,
'getToken'
);
final
hasCloseApp
=
_hasProperty
(
module
,
'closeApp'
);
if
(!
hasGetToken
||
!
hasCloseApp
)
{
if
(!
_hasProperty
(
module
,
'getToken'
)
||
!
_hasProperty
(
module
,
'closeApp'
))
{
_lastError
=
'x-app-sdk module missing required exports'
;
print
(
'❌ XAppSDKService:
$_lastError
'
);
return
null
;
}
_sdkModule
=
module
;
_browserMode
=
_detectBrowserMode
(
module
);
return
_sdkModule
;
}
catch
(
e
)
{
_lastError
=
'Failed to load x-app-sdk module:
$e
'
;
...
...
@@ -253,4 +516,17 @@ class XAppSDKService {
return
false
;
}
}
bool
_detectBrowserMode
(
dynamic
module
)
{
try
{
final
fltSdk
=
getProperty
(
module
,
'fltSDK'
);
if
(
fltSdk
!=
null
&&
_hasProperty
(
fltSdk
,
'getBrowserMode'
))
{
final
mode
=
callMethod
(
fltSdk
,
'getBrowserMode'
,
<
dynamic
>[]);
if
(
mode
is
bool
)
{
return
mode
;
}
}
}
catch
(
_
)
{}
return
false
;
}
}
lib/widgets/back_button.dart
View file @
a0bcdab2
...
...
@@ -37,11 +37,11 @@ class CustomBackButton extends StatelessWidget {
icon:
Icon
(
Icons
.
arrow_back_ios_rounded
,
color:
iconColor
??
BaseColor
.
second600
,
size:
iconSize
??
16
),
onPressed:
onPressed
??
()
{
()
async
{
if
(
Get
.
key
.
currentState
?.
canPop
()
==
true
)
{
Get
.
back
();
}
else
{
DataPreference
.
instance
.
clearData
();
await
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
onboardingScreen
);
}
},
...
...
web/js/x_app_sdk_loader.js
View file @
a0bcdab2
...
...
@@ -8,18 +8,46 @@ const SDK_CANDIDATE_PATHS = CANDIDATE_RELATIVE_PATHS.map((relative) =>
new
URL
(
relative
,
import
.
meta
.
url
).
href
,
);
const
REQUIRED_METHODS
=
[
'
getToken
'
,
'
closeApp
'
];
const
OPTIONAL_METHODS
=
[
'
configUIApp
'
,
'
call
'
,
'
sms
'
,
'
vibrate
'
,
'
currentLocation
'
,
'
requestLocationPermission
'
,
'
openPickerImage
'
,
'
openPickerFile
'
,
'
listenNotifiactionEvent
'
,
'
paymentRequest
'
,
'
listenPaymentEvent
'
,
'
premissionsRequest
'
,
'
saveStore
'
,
'
getStore
'
,
'
clearStore
'
,
'
getInfo
'
,
];
const
KNOWN_METHODS
=
[...
REQUIRED_METHODS
,
...
OPTIONAL_METHODS
];
let
cachedModulePromise
=
null
;
function
ensureSdkShape
(
module
,
source
)
{
if
(
!
module
)
{
throw
new
Error
(
`Module from
${
source
}
is undefined`
);
}
const
requiredMethods
=
[
'
getToken
'
,
'
closeApp
'
];
for
(
const
method
of
requiredMethods
)
{
for
(
const
method
of
REQUIRED_METHODS
)
{
if
(
typeof
module
[
method
]
!==
'
function
'
)
{
throw
new
Error
(
`Module from
${
source
}
is missing required export:
${
method
}
`
);
}
}
const
missingOptional
=
OPTIONAL_METHODS
.
filter
(
(
method
)
=>
typeof
module
[
method
]
!==
'
function
'
,
);
if
(
missingOptional
.
length
)
{
console
.
warn
(
`⚠️ x-app-sdk module from
${
source
}
missing optional exports:
${
missingOptional
.
join
(
'
,
'
)}
`
,
);
}
}
async
function
importWithFallback
()
{
...
...
@@ -72,22 +100,30 @@ export async function bootstrapXAppSdk() {
return
module
;
}
async
function
getToken
()
{
const
module
=
await
loadXAppSdkModule
();
return
module
.
getToken
();
function
createMethodProxy
(
methodName
)
{
return
async
(...
args
)
=>
{
const
module
=
await
loadXAppSdkModule
();
const
target
=
module
?.[
methodName
];
if
(
typeof
target
!==
'
function
'
)
{
const
available
=
Object
.
keys
(
module
??
{});
throw
new
Error
(
`x-app-sdk method "
${
methodName
}
" is not available. Exported methods:
${
available
.
join
(
'
,
'
)}
`
,
);
}
return
target
(...
args
);
};
}
async
function
closeApp
(
payload
)
{
const
module
=
await
loadXAppSdkModule
(
);
return
module
.
closeApp
(
payload
)
;
}
const
loaderApiMethods
=
KNOWN_METHODS
.
reduce
((
acc
,
methodName
)
=>
{
acc
[
methodName
]
=
createMethodProxy
(
methodName
);
return
acc
;
}
,
{});
const
loaderApi
=
{
loadXAppSdkModule
,
bootstrapXAppSdk
,
resetCachedModule
,
getToken
,
closeApp
,
...
loaderApiMethods
,
};
if
(
!
globalThis
.
__xAppSdkLoader
)
{
...
...
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment