mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-26 07:28:45 -07:00
446a23da2b
Initial v0.1 (commit df9dac0) used PhotinoNET wrapping WebView2 + HTML/JS,
but rendered as a blank window on first run — WebView2 + file:// URI
quirks. Switching to native WPF: built into the .NET 9 Windows SDK
(no NuGet), single-file self-contained publish, instant window open.
All Backend/*.cs orchestration logic carried over unchanged (they're
UI-framework-agnostic). Replaced:
- Program.cs (PhotinoNET bootstrap) -> App.xaml(.cs) + MainWindow.xaml(.cs)
- ui/index.html + style.css + app.js -> MainWindow.xaml (XAML views) +
App.xaml resource styles
- Backend/MessageBridge.cs (JS<->C# router) -> direct method calls in
MainWindow.xaml.cs code-behind
RecRoomPicker now uses Microsoft.Win32.OpenFolderDialog (added in .NET 8)
instead of WinForms FolderBrowserDialog — keeps the project WPF-only.
Backend files needed explicit 'using System.IO;' since the WPF SDK
doesn't include it in implicit usings.
90 lines
3.3 KiB
C#
90 lines
3.3 KiB
C#
using System.IO;
|
|
using System.Diagnostics;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace DorkNet.Launcher.Backend;
|
|
|
|
/// <summary>Wraps Cloudflare's <c>cloudflared</c> quick-tunnel so the
|
|
/// host doesn't need to open ports / configure DNS. Each launch gets
|
|
/// a fresh random <c>*.trycloudflare.com</c> URL.
|
|
///
|
|
/// <para>Requires <c>cloudflared.exe</c> on PATH or alongside the
|
|
/// launcher binary. If absent, returns a clear error and the host
|
|
/// can fall back to LAN-only play.</para></summary>
|
|
public sealed class Tunnel : IAsyncDisposable
|
|
{
|
|
private Process? _process;
|
|
private TaskCompletionSource<string>? _urlReady;
|
|
public string? PublicUrl { get; private set; }
|
|
|
|
public async Task<string> StartAsync(int localPort, CancellationToken ct = default)
|
|
{
|
|
var exe = FindCloudflared() ??
|
|
throw new FileNotFoundException(
|
|
"cloudflared not found. Install from " +
|
|
"https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/ " +
|
|
"and put cloudflared.exe on PATH (or next to the DorkNet launcher).");
|
|
|
|
_urlReady = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = exe,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
};
|
|
psi.ArgumentList.Add("tunnel");
|
|
psi.ArgumentList.Add("--no-autoupdate");
|
|
psi.ArgumentList.Add("--url");
|
|
psi.ArgumentList.Add($"http://127.0.0.1:{localPort}");
|
|
|
|
_process = Process.Start(psi) ??
|
|
throw new InvalidOperationException("Failed to spawn cloudflared.exe");
|
|
_process.OutputDataReceived += (_, e) => ParseLine(e.Data);
|
|
_process.ErrorDataReceived += (_, e) => ParseLine(e.Data);
|
|
_process.BeginOutputReadLine();
|
|
_process.BeginErrorReadLine();
|
|
|
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
timeoutCts.CancelAfter(TimeSpan.FromSeconds(30));
|
|
PublicUrl = await _urlReady.Task.WaitAsync(timeoutCts.Token);
|
|
return PublicUrl;
|
|
}
|
|
|
|
private static readonly Regex UrlRegex = new(
|
|
@"https://[a-z0-9\-]+\.trycloudflare\.com",
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
private void ParseLine(string? line)
|
|
{
|
|
if (line is null || _urlReady is null || _urlReady.Task.IsCompleted) return;
|
|
var match = UrlRegex.Match(line);
|
|
if (match.Success) _urlReady.TrySetResult(match.Value);
|
|
}
|
|
|
|
private static string? FindCloudflared()
|
|
{
|
|
var sideBySide = Path.Combine(AppContext.BaseDirectory, "cloudflared.exe");
|
|
if (File.Exists(sideBySide)) return sideBySide;
|
|
var onPath = Environment.GetEnvironmentVariable("PATH")?
|
|
.Split(';')
|
|
.Select(p => Path.Combine(p, "cloudflared.exe"))
|
|
.FirstOrDefault(File.Exists);
|
|
return onPath;
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_process is null) return;
|
|
try
|
|
{
|
|
if (!_process.HasExited) _process.Kill(entireProcessTree: true);
|
|
await _process.WaitForExitAsync();
|
|
}
|
|
catch { }
|
|
_process.Dispose();
|
|
}
|
|
}
|