diff --git a/OneGateApp/Pages/LaunchDAppPage.xaml b/OneGateApp/Pages/LaunchDAppPage.xaml
index 5f5b0e4..2a65702 100644
--- a/OneGateApp/Pages/LaunchDAppPage.xaml
+++ b/OneGateApp/Pages/LaunchDAppPage.xaml
@@ -26,5 +26,38 @@
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OneGateApp/Pages/LaunchDAppPage.xaml.cs b/OneGateApp/Pages/LaunchDAppPage.xaml.cs
index 25f0398..a4165e1 100644
--- a/OneGateApp/Pages/LaunchDAppPage.xaml.cs
+++ b/OneGateApp/Pages/LaunchDAppPage.xaml.cs
@@ -13,12 +13,16 @@
using NeoOrder.OneGate.Services.RemoteDebug;
using System.Net;
using System.Net.Http.Json;
+using System.Text.Json;
using System.Text.Json.Nodes;
namespace NeoOrder.OneGate.Pages;
public partial class LaunchDAppPage : ContentPage, IQueryAttributable, IRemoteDebugSessionHost
{
+ const int DAppLoadTimeoutMs = 30000;
+ const int DAppPreparationDurationMs = 12000;
+
readonly IServiceProvider serviceProvider;
readonly ProtocolSettings protocolSettings;
readonly IWalletProvider walletProvider;
@@ -28,13 +32,34 @@ public partial class LaunchDAppPage : ContentPage, IQueryAttributable, IRemoteDe
readonly HttpClient httpClient;
readonly RpcServer rpcServer;
readonly RpcClient rpcClient;
+ CancellationTokenSource? dappLoadCancellation;
+ CancellationTokenSource? dappPreparationCancellation;
+ Uri? pendingAppLinkUri;
+ string? url;
RemoteDebugService? remoteDebugService;
string? remoteDebugSessionId;
public required DApp DApp { get; set { field = value; OnPropertyChanged(); } }
- public required string Url { get; set { field = value; OnPropertyChanged(); } }
+ public string? Url
+ {
+ get => url;
+ set
+ {
+ if (url == value) return;
+ url = value;
+ if (!string.IsNullOrWhiteSpace(url))
+ BeginDAppLoad();
+ OnPropertyChanged();
+ }
+ }
public bool IsFavorite { get; set { field = value; OnPropertyChanged(); } }
public bool IsDeveloperToolsEnabled { get; set { field = value; OnPropertyChanged(); } }
+ public bool IsDAppLoading { get; set { field = value; OnPropertyChanged(); } }
+ public bool IsDAppPreparing { get; set { field = value; OnPropertyChanged(); } }
+ public bool HasDAppLoadError { get; set { field = value; OnPropertyChanged(); } }
+ public string DAppLoadErrorTitle { get; set { field = value; OnPropertyChanged(); } } = "";
+ public string DAppLoadErrorMessage { get; set { field = value; OnPropertyChanged(); } } = "";
+ public string RetryText => Strings.Retry;
bool IsRemoteDebugSession => remoteDebugSessionId is not null;
public LaunchDAppPage(IServiceProvider serviceProvider, ProtocolSettings protocolSettings, IWalletProvider walletProvider, WalletAuthorizationService walletAuthorizationService, ApplicationDbContext dbContext, ActivityLogService activityLogService, HttpClient httpClient, RpcClient rpcClient, IHomeShortcutService homeShortcutService)
@@ -69,6 +94,8 @@ internal void ConfigureRemoteDebug(string sessionId, RemoteDebugService service)
protected override void OnDisappearing()
{
base.OnDisappearing();
+ CancelDAppLoadTimeout();
+ CancelDAppPreparation();
if (remoteDebugSessionId is not null && remoteDebugService is not null)
remoteDebugService.NotifySessionHostClosed(remoteDebugSessionId, this);
}
@@ -121,7 +148,7 @@ public async Task GetRemoteStatusAsync()
status["target"] = "onegate";
status["state"] = "active";
status["href"] ??= Url;
- status["origin"] ??= new Uri(Url).GetLeftPart(UriPartial.Authority);
+ status["origin"] ??= new Uri(Url!).GetLeftPart(UriPartial.Authority);
return status;
}
@@ -170,18 +197,9 @@ public async void ApplyQueryAttributes(IDictionary query)
Uri uri = query["uri"] as Uri ?? new(WebUtility.UrlDecode((string)query["uri"]));
if (LaunchDAppAction.TryCreate(uri) is LaunchDAppAction action)
{
- var response = await httpClient.GetAsync($"/api/dapp/{action.AppId}");
- if (!response.IsSuccessStatusCode)
- {
- await this.GoBackOrCloseAsync();
- return;
- }
- DApp = (await response.Content.ReadFromJsonAsync())!;
- if (string.IsNullOrEmpty(uri.Query))
- Url = DApp.Url;
- else
- Url = DApp.Url + uri.Query;
- UpdateReportButton();
+ pendingAppLinkUri = uri;
+ if (!await TryLoadAppLinkAsync(action, uri)) return;
+ pendingAppLinkUri = null;
}
else
{
@@ -197,6 +215,11 @@ public async void ApplyQueryAttributes(IDictionary query)
UpdateReportButton();
}
}
+ await RecordDAppOpenAsync();
+ }
+
+ async Task RecordDAppOpenAsync()
+ {
if (DApp.Id > 0)
{
List? favorites = await dbContext.Settings.GetAsync>("dapps/favorite");
@@ -254,7 +277,171 @@ async void OnNavigating(object sender, WebNavigatingEventArgs e)
{
e.Cancel = true;
await Toast.Show(Strings.RedirectionBlockedText);
+ IsDAppLoading = false;
+ IsDAppPreparing = false;
+ HasDAppLoadError = false;
+ return;
+ }
+ BeginDAppLoad();
+ }
+
+ void OnNavigated(object sender, WebNavigatedEventArgs e)
+ {
+ if (e.Url == "about:blank") return;
+ CancelDAppLoadTimeout();
+ IsDAppLoading = false;
+ if (e.Result == WebNavigationResult.Success || e.Result == WebNavigationResult.Cancel)
+ {
+ HasDAppLoadError = false;
+ if (e.Result == WebNavigationResult.Success)
+ BeginDAppPreparation();
+ else
+ IsDAppPreparing = false;
+ return;
+ }
+ ShowDAppLoadError(e.Url, e.Result);
+ }
+
+ async void OnRetryDAppClicked(object sender, EventArgs e)
+ {
+ if (pendingAppLinkUri is Uri uri && LaunchDAppAction.TryCreate(uri) is LaunchDAppAction action)
+ {
+ if (await TryLoadAppLinkAsync(action, uri))
+ {
+ pendingAppLinkUri = null;
+ await RecordDAppOpenAsync();
+ }
+ return;
}
+ if (string.IsNullOrWhiteSpace(Url)) return;
+ BeginDAppLoad();
+ webView.Reload();
+ }
+
+ async Task TryLoadAppLinkAsync(LaunchDAppAction action, Uri uri)
+ {
+ CancelDAppLoadTimeout();
+ CancelDAppPreparation();
+ IsDAppLoading = true;
+ HasDAppLoadError = false;
+ try
+ {
+ using HttpResponseMessage response = await httpClient.GetAsync($"/api/dapp/{action.AppId}");
+ if (!response.IsSuccessStatusCode)
+ {
+ await this.GoBackOrCloseAsync();
+ return false;
+ }
+ DApp? dapp = await response.Content.ReadFromJsonAsync();
+ if (dapp is null)
+ {
+ ShowDAppLoadError(uri.AbsoluteUri, WebNavigationResult.Failure);
+ return false;
+ }
+ DApp = dapp;
+ Url = string.IsNullOrEmpty(uri.Query) ? DApp.Url : DApp.Url + uri.Query;
+ UpdateReportButton();
+ return true;
+ }
+ catch (HttpRequestException)
+ {
+ ShowDAppLoadError(uri.AbsoluteUri, WebNavigationResult.Failure);
+ return false;
+ }
+ catch (TaskCanceledException)
+ {
+ ShowDAppLoadError(uri.AbsoluteUri, WebNavigationResult.Timeout);
+ return false;
+ }
+ catch (JsonException)
+ {
+ ShowDAppLoadError(uri.AbsoluteUri, WebNavigationResult.Failure);
+ return false;
+ }
+ }
+
+ void BeginDAppLoad()
+ {
+ CancelDAppLoadTimeout();
+ CancelDAppPreparation();
+ IsDAppLoading = true;
+ IsDAppPreparing = false;
+ HasDAppLoadError = false;
+ dappLoadCancellation = new();
+ _ = WatchDAppLoadAsync(Url ?? DApp?.Url ?? string.Empty, dappLoadCancellation.Token);
+ }
+
+ void ShowDAppLoadError(string failedUrl, WebNavigationResult result)
+ {
+ CancelDAppLoadTimeout();
+ CancelDAppPreparation();
+ IsDAppLoading = false;
+ string appName = DApp?.NameLocalizer.Localize() ?? GetHostOrUrl(failedUrl);
+ DAppLoadErrorTitle = Strings.DAppLoadFailed;
+ DAppLoadErrorMessage = string.Format(Strings.DAppLoadFailedText, appName);
+ HasDAppLoadError = true;
+ }
+
+ void BeginDAppPreparation()
+ {
+ CancelDAppLoadTimeout();
+ CancelDAppPreparation();
+ dappPreparationCancellation = new();
+ IsDAppPreparing = true;
+ _ = WatchDAppPreparationAsync(dappPreparationCancellation.Token);
+ }
+
+ void CancelDAppLoadTimeout()
+ {
+ dappLoadCancellation?.Cancel();
+ dappLoadCancellation?.Dispose();
+ dappLoadCancellation = null;
+ }
+
+ async Task WatchDAppLoadAsync(string failedUrl, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await Task.Delay(DAppLoadTimeoutMs, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ return;
+ }
+ if (!cancellationToken.IsCancellationRequested)
+ MainThread.BeginInvokeOnMainThread(() => ShowDAppLoadError(failedUrl, WebNavigationResult.Timeout));
+ }
+
+ void CancelDAppPreparation()
+ {
+ dappPreparationCancellation?.Cancel();
+ dappPreparationCancellation?.Dispose();
+ dappPreparationCancellation = null;
+ IsDAppPreparing = false;
+ }
+
+ async Task WatchDAppPreparationAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ await Task.Delay(DAppPreparationDurationMs, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ return;
+ }
+ finally
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ MainThread.BeginInvokeOnMainThread(() => IsDAppPreparing = false);
+ }
+ }
+
+ static string GetHostOrUrl(string url)
+ {
+ return Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) && !string.IsNullOrWhiteSpace(uri.Host)
+ ? uri.Host
+ : url;
}
string CreateDocumentStartScript()
diff --git a/OneGateApp/Properties/Strings.Designer.cs b/OneGateApp/Properties/Strings.Designer.cs
index 7a727b2..9c824f4 100644
--- a/OneGateApp/Properties/Strings.Designer.cs
+++ b/OneGateApp/Properties/Strings.Designer.cs
@@ -765,6 +765,42 @@ internal static string DAppAuthorizationText {
}
}
+ ///
+ /// 查找类似 DApp did not load 的本地化字符串。
+ ///
+ internal static string DAppLoadFailed {
+ get {
+ return ResourceManager.GetString("DAppLoadFailed", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 {0} could not be loaded. Check your connection and try again. Status: {1} 的本地化字符串。
+ ///
+ internal static string DAppLoadFailedText {
+ get {
+ return ResourceManager.GetString("DAppLoadFailedText", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 Preparing DApp 的本地化字符串。
+ ///
+ internal static string DAppPreparing {
+ get {
+ return ResourceManager.GetString("DAppPreparing", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 This dApp may need more time to finish loading. 的本地化字符串。
+ ///
+ internal static string DAppPreparingText {
+ get {
+ return ResourceManager.GetString("DAppPreparingText", resourceCulture);
+ }
+ }
+
///
/// 查找类似 DApp Debug Panel 的本地化字符串。
///
diff --git a/OneGateApp/Properties/Strings.de.resx b/OneGateApp/Properties/Strings.de.resx
index 7dd2eeb..5c21b3a 100644
--- a/OneGateApp/Properties/Strings.de.resx
+++ b/OneGateApp/Properties/Strings.de.resx
@@ -749,6 +749,18 @@ Möchten Sie fortfahren?
Rückkehr zur DApp fehlgeschlagen
+
+ DApp wurde nicht geladen
+
+
+ {0} konnte nicht geladen werden. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.
+
+
+ DApp wird vorbereitet
+
+
+ Diese dApp benötigt möglicherweise etwas mehr Zeit zum Laden.
+
Teilen
diff --git a/OneGateApp/Properties/Strings.es.resx b/OneGateApp/Properties/Strings.es.resx
index 3c5ef0a..7e86df5 100644
--- a/OneGateApp/Properties/Strings.es.resx
+++ b/OneGateApp/Properties/Strings.es.resx
@@ -749,6 +749,18 @@ Después de desactivarla, deberá introducir manualmente la contraseña de la bi
No se pudo volver a la DApp
+
+ La DApp no se cargó
+
+
+ No se pudo cargar {0}. Revisa tu conexión e inténtalo de nuevo.
+
+
+ Preparando la DApp
+
+
+ Esta dApp puede necesitar más tiempo para terminar de cargar.
+
Compartir
diff --git a/OneGateApp/Properties/Strings.fr.resx b/OneGateApp/Properties/Strings.fr.resx
index c91902f..52e0d46 100644
--- a/OneGateApp/Properties/Strings.fr.resx
+++ b/OneGateApp/Properties/Strings.fr.resx
@@ -749,6 +749,18 @@ Voulez-vous continuer ?
Impossible de revenir à la DApp
+
+ La DApp n’a pas été chargée
+
+
+ {0} n’a pas pu être chargée. Vérifiez votre connexion et réessayez.
+
+
+ Préparation de la DApp
+
+
+ Cette dApp peut avoir besoin de plus de temps pour terminer son chargement.
+
Partager
diff --git a/OneGateApp/Properties/Strings.id.resx b/OneGateApp/Properties/Strings.id.resx
index 658a186..b869d6b 100644
--- a/OneGateApp/Properties/Strings.id.resx
+++ b/OneGateApp/Properties/Strings.id.resx
@@ -749,6 +749,18 @@ Apakah Anda ingin melanjutkan?
Tidak dapat kembali ke DApp
+
+ DApp tidak dimuat
+
+
+ {0} tidak dapat dimuat. Periksa koneksi Anda dan coba lagi.
+
+
+ Menyiapkan DApp
+
+
+ dApp ini mungkin memerlukan lebih banyak waktu untuk selesai dimuat.
+
Bagikan
diff --git a/OneGateApp/Properties/Strings.it.resx b/OneGateApp/Properties/Strings.it.resx
index 8c8f0fe..b617ec7 100644
--- a/OneGateApp/Properties/Strings.it.resx
+++ b/OneGateApp/Properties/Strings.it.resx
@@ -749,6 +749,18 @@ Vuoi continuare?
Impossibile tornare alla DApp
+
+ La DApp non è stata caricata
+
+
+ Impossibile caricare {0}. Controlla la connessione e riprova.
+
+
+ Preparazione della DApp
+
+
+ Questa dApp potrebbe richiedere più tempo per completare il caricamento.
+
Condividi
diff --git a/OneGateApp/Properties/Strings.ja.resx b/OneGateApp/Properties/Strings.ja.resx
index 702710a..6f5caf0 100644
--- a/OneGateApp/Properties/Strings.ja.resx
+++ b/OneGateApp/Properties/Strings.ja.resx
@@ -749,6 +749,18 @@
DAppに戻れませんでした
+
+ DApp を読み込めませんでした
+
+
+ {0} を読み込めませんでした。接続を確認してからもう一度お試しください。
+
+
+ DApp を準備中
+
+
+ このdAppは読み込みが完了するまでに時間がかかる場合があります。
+
共有
diff --git a/OneGateApp/Properties/Strings.ko.resx b/OneGateApp/Properties/Strings.ko.resx
index 832c342..9a8e0dc 100644
--- a/OneGateApp/Properties/Strings.ko.resx
+++ b/OneGateApp/Properties/Strings.ko.resx
@@ -749,6 +749,18 @@
DApp으로 돌아갈 수 없습니다
+
+ DApp을 불러오지 못했습니다
+
+
+ {0}을(를) 불러올 수 없습니다. 연결을 확인한 후 다시 시도하세요.
+
+
+ DApp 준비 중
+
+
+ 이 dApp은 로딩이 완료되는 데 시간이 더 걸릴 수 있습니다.
+
공유
diff --git a/OneGateApp/Properties/Strings.nl.resx b/OneGateApp/Properties/Strings.nl.resx
index dc515d5..81f8f19 100644
--- a/OneGateApp/Properties/Strings.nl.resx
+++ b/OneGateApp/Properties/Strings.nl.resx
@@ -749,6 +749,18 @@ Wilt u doorgaan?
Kan niet terugkeren naar de DApp
+
+ DApp is niet geladen
+
+
+ {0} kan niet worden geladen. Controleer je verbinding en probeer het opnieuw.
+
+
+ DApp voorbereiden
+
+
+ Deze dApp heeft mogelijk meer tijd nodig om te laden.
+
Delen
diff --git a/OneGateApp/Properties/Strings.pt-BR.resx b/OneGateApp/Properties/Strings.pt-BR.resx
index ca434f9..c297b88 100644
--- a/OneGateApp/Properties/Strings.pt-BR.resx
+++ b/OneGateApp/Properties/Strings.pt-BR.resx
@@ -749,6 +749,18 @@ Deseja continuar?
Não foi possível voltar para a DApp
+
+ A DApp não foi carregada
+
+
+ Não foi possível carregar {0}. Verifique sua conexão e tente novamente.
+
+
+ Preparando a DApp
+
+
+ Este dApp pode precisar de mais tempo para concluir o carregamento.
+
Compartilhar
diff --git a/OneGateApp/Properties/Strings.resx b/OneGateApp/Properties/Strings.resx
index 61a49e5..cedd931 100644
--- a/OneGateApp/Properties/Strings.resx
+++ b/OneGateApp/Properties/Strings.resx
@@ -749,6 +749,18 @@ Do you want to continue?
Unable to return to the DApp
+
+ DApp did not load
+
+
+ {0} could not be loaded. Check your connection and try again.
+
+
+ Preparing DApp
+
+
+ This dApp may need more time to finish loading.
+
Share
diff --git a/OneGateApp/Properties/Strings.ru.resx b/OneGateApp/Properties/Strings.ru.resx
index 80b5cdb..51b266a 100644
--- a/OneGateApp/Properties/Strings.ru.resx
+++ b/OneGateApp/Properties/Strings.ru.resx
@@ -749,6 +749,18 @@
Не удалось вернуться в DApp
+
+ DApp не загрузилась
+
+
+ Не удалось загрузить {0}. Проверьте подключение и повторите попытку.
+
+
+ Подготовка DApp
+
+
+ Этому dApp может потребоваться больше времени для завершения загрузки.
+
Поделиться
diff --git a/OneGateApp/Properties/Strings.tr.resx b/OneGateApp/Properties/Strings.tr.resx
index 2a9177c..44cda97 100644
--- a/OneGateApp/Properties/Strings.tr.resx
+++ b/OneGateApp/Properties/Strings.tr.resx
@@ -749,6 +749,18 @@ Devam etmek istiyor musunuz?
DApp'e geri dönülemedi
+
+ DApp yüklenmedi
+
+
+ {0} yüklenemedi. Bağlantınızı kontrol edip tekrar deneyin.
+
+
+ DApp hazırlanıyor
+
+
+ Bu dApp'in yüklenmesinin tamamlanması daha uzun sürebilir.
+
Paylaş
diff --git a/OneGateApp/Properties/Strings.vi.resx b/OneGateApp/Properties/Strings.vi.resx
index d44407d..15ef022 100644
--- a/OneGateApp/Properties/Strings.vi.resx
+++ b/OneGateApp/Properties/Strings.vi.resx
@@ -749,6 +749,18 @@ Bạn có muốn tiếp tục không?
Không thể quay lại DApp
+
+ DApp chưa được tải
+
+
+ Không thể tải {0}. Hãy kiểm tra kết nối rồi thử lại.
+
+
+ Đang chuẩn bị DApp
+
+
+ dApp này có thể cần thêm thời gian để tải xong.
+
Chia sẻ
diff --git a/OneGateApp/Properties/Strings.zh-Hans.resx b/OneGateApp/Properties/Strings.zh-Hans.resx
index adc591e..88304a4 100644
--- a/OneGateApp/Properties/Strings.zh-Hans.resx
+++ b/OneGateApp/Properties/Strings.zh-Hans.resx
@@ -749,6 +749,18 @@
无法返回 DApp
+
+ DApp 未能加载
+
+
+ {0} 暂时无法加载。请检查网络连接后重试。
+
+
+ 正在准备 DApp
+
+
+ 此 dApp 可能需要更多时间才能完成加载。
+
分享
diff --git a/OneGateApp/Properties/Strings.zh-Hant.resx b/OneGateApp/Properties/Strings.zh-Hant.resx
index a4efc9b..e93295e 100644
--- a/OneGateApp/Properties/Strings.zh-Hant.resx
+++ b/OneGateApp/Properties/Strings.zh-Hant.resx
@@ -725,6 +725,18 @@
無法返回 DApp
+
+ DApp 未能載入
+
+
+ {0} 暫時無法載入。請檢查網路連線後重試。
+
+
+ 正在準備 DApp
+
+
+ 此 dApp 可能需要更多時間才能完成載入。
+
分享