Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions OneGateApp/Pages/LaunchDAppPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,38 @@
<ToolbarItem x:Name="developerToolsButton" Order="Secondary" Text="{x:Static og:Strings.DAppDebugPanel}" Clicked="OnDeveloperToolsClicked" />
<ToolbarItem x:Name="reportButton" Order="Secondary" Text="{x:Static og:Strings.Report}" Clicked="OnReportClicked" />
</ContentPage.ToolbarItems>
<og:BridgeWebView x:Name="webView" Source="{Binding Url}" Navigating="OnNavigating" InvokedFromJavaScript="OnInvokedFromJavaScript" />
</ContentPage>
<Grid>
<og:BridgeWebView x:Name="webView" Source="{Binding Url}" Navigating="OnNavigating" Navigated="OnNavigated" InvokedFromJavaScript="OnInvokedFromJavaScript" />
<Grid BackgroundColor="{toolkit:AppThemeResource PageBackground}" IsVisible="{Binding IsDAppLoading}">
<VerticalStackLayout Spacing="12" HorizontalOptions="Center" VerticalOptions="Center">
<ActivityIndicator IsRunning="{Binding IsDAppLoading}" WidthRequest="32" HeightRequest="32" />
<Label FontAttributes="Bold" HorizontalOptions="Center">
<Label.FormattedText>
<FormattedString>
<Span Text="{x:Static og:Strings.Loading}" />
<Span Text="…" />
</FormattedString>
</Label.FormattedText>
</Label>
</VerticalStackLayout>
</Grid>
<Grid Padding="16" InputTransparent="True" IsVisible="{Binding IsDAppPreparing}" VerticalOptions="Start">
<Border StyleClass="Card" HorizontalOptions="Center" MaximumWidthRequest="420">
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="12">
<Border BackgroundColor="{toolkit:AppThemeResource Brand}" StrokeThickness="0" StrokeShape="Ellipse" WidthRequest="10" HeightRequest="10" VerticalOptions="Center" />
<VerticalStackLayout Grid.Column="1" Spacing="2">
<Label Text="{x:Static og:Strings.DAppPreparing}" FontAttributes="Bold" LineBreakMode="TailTruncation" MaxLines="1" />
<Label StyleClass="Secondary" Text="{x:Static og:Strings.DAppPreparingText}" FontSize="12" LineBreakMode="TailTruncation" MaxLines="2" />
</VerticalStackLayout>
</Grid>
</Border>
</Grid>
<Grid BackgroundColor="{toolkit:AppThemeResource PageBackground}" IsVisible="{Binding HasDAppLoadError}" Padding="32">
<VerticalStackLayout Spacing="16" HorizontalOptions="Center" VerticalOptions="Center" MaximumWidthRequest="420">
<Label Text="{Binding DAppLoadErrorTitle}" FontAttributes="Bold" FontSize="22" HorizontalTextAlignment="Center" />
<Label StyleClass="Secondary" Text="{Binding DAppLoadErrorMessage}" HorizontalTextAlignment="Center" />
<Button Text="{Binding RetryText}" Padding="36,14" Clicked="OnRetryDAppClicked" />
</VerticalStackLayout>
</Grid>
</Grid>
</ContentPage>
215 changes: 201 additions & 14 deletions OneGateApp/Pages/LaunchDAppPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -121,7 +148,7 @@ public async Task<JsonObject> 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;
}

Expand Down Expand Up @@ -170,18 +197,9 @@ public async void ApplyQueryAttributes(IDictionary<string, object> 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<DApp>())!;
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
{
Expand All @@ -197,6 +215,11 @@ public async void ApplyQueryAttributes(IDictionary<string, object> query)
UpdateReportButton();
}
}
await RecordDAppOpenAsync();
}

async Task RecordDAppOpenAsync()
{
if (DApp.Id > 0)
{
List<int>? favorites = await dbContext.Settings.GetAsync<List<int>>("dapps/favorite");
Expand Down Expand Up @@ -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<bool> 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<DApp>();
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()
Expand Down
36 changes: 36 additions & 0 deletions OneGateApp/Properties/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions OneGateApp/Properties/Strings.de.resx
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,18 @@ Möchten Sie fortfahren?</value>
<data name="OpenDAppFailedText" xml:space="preserve">
<value>Rückkehr zur DApp fehlgeschlagen</value>
</data>
<data name="DAppLoadFailed" xml:space="preserve">
<value>DApp wurde nicht geladen</value>
</data>
<data name="DAppLoadFailedText" xml:space="preserve">
<value>{0} konnte nicht geladen werden. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.</value>
</data>
<data name="DAppPreparing" xml:space="preserve">
<value>DApp wird vorbereitet</value>
</data>
<data name="DAppPreparingText" xml:space="preserve">
<value>Diese dApp benötigt möglicherweise etwas mehr Zeit zum Laden.</value>
</data>
<data name="Share" xml:space="preserve">
<value>Teilen</value>
</data>
Expand Down
12 changes: 12 additions & 0 deletions OneGateApp/Properties/Strings.es.resx
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,18 @@ Después de desactivarla, deberá introducir manualmente la contraseña de la bi
<data name="OpenDAppFailedText" xml:space="preserve">
<value>No se pudo volver a la DApp</value>
</data>
<data name="DAppLoadFailed" xml:space="preserve">
<value>La DApp no se cargó</value>
</data>
<data name="DAppLoadFailedText" xml:space="preserve">
<value>No se pudo cargar {0}. Revisa tu conexión e inténtalo de nuevo.</value>
</data>
<data name="DAppPreparing" xml:space="preserve">
<value>Preparando la DApp</value>
</data>
<data name="DAppPreparingText" xml:space="preserve">
<value>Esta dApp puede necesitar más tiempo para terminar de cargar.</value>
</data>
<data name="Share" xml:space="preserve">
<value>Compartir</value>
</data>
Expand Down
Loading