mirror of
https://github.com/scratchfoundation/scratch-link.git
synced 2024-11-14 19:05:03 -05:00
make ScratchLinkApp to take the place of MAUI app
Note that this builds and runs, but doesn't work: the Xamarin.Mac implementation of `System.Net` always sets `IsWebSocketRequest` to `false` and doesn't actually support WebSockets connections as a server.
This commit is contained in:
parent
2c9e4c600a
commit
8be1d8b021
7 changed files with 166 additions and 99 deletions
|
@ -11,6 +11,7 @@ using System.Net.WebSockets;
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ScratchLink.Extensions;
|
||||
using ScratchLink.JsonRpc;
|
||||
|
||||
|
@ -28,7 +29,7 @@ internal abstract class BLESession<TUUID> : Session
|
|||
public BLESession(WebSocketContext context)
|
||||
: base(context)
|
||||
{
|
||||
this.GattHelpers = IPlatformApplication.Current.Services.GetService<GattHelpers<TUUID>>();
|
||||
this.GattHelpers = ScratchLinkApp.Current.Services.GetService<GattHelpers<TUUID>>();
|
||||
this.AllowedServices = new ();
|
||||
this.Handlers["discover"] = this.HandleDiscover;
|
||||
this.Handlers["connect"] = this.HandleConnect;
|
||||
|
|
150
scratch-link-common/ScratchLinkApp.cs
Normal file
150
scratch-link-common/ScratchLinkApp.cs
Normal file
|
@ -0,0 +1,150 @@
|
|||
// <copyright file="ScratchLinkApp.cs" company="Scratch Foundation">
|
||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ScratchLink;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ScratchLink.BLE;
|
||||
|
||||
/// <summary>
|
||||
/// Main entry point for Scratch Link and central service provider for dependency injection.
|
||||
/// </summary>
|
||||
public class ScratchLinkApp
|
||||
{
|
||||
private const int WebSocketPort = 20111;
|
||||
|
||||
private readonly SessionManager sessionManager;
|
||||
private readonly WebSocketListener webSocketListener;
|
||||
|
||||
private ScratchLinkApp(IServiceProvider platformServicesProvider)
|
||||
{
|
||||
this.Services = platformServicesProvider;
|
||||
if (Current != null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempt to create a second app instance");
|
||||
}
|
||||
|
||||
Current = this;
|
||||
|
||||
this.sessionManager = this.Services.GetService<SessionManager>();
|
||||
this.webSocketListener = new ()
|
||||
{
|
||||
OnWebSocketConnection = (webSocketContext) =>
|
||||
{
|
||||
this.sessionManager.ClientDidConnect(webSocketContext);
|
||||
},
|
||||
OnOtherConnection = (context) =>
|
||||
{
|
||||
context.Response.Headers.Clear();
|
||||
context.Response.SendChunked = false;
|
||||
context.Response.StatusCode = 426; // Upgrade Required
|
||||
context.Response.OutputStream.Write(Encoding.UTF8.GetBytes("WebSockets required"));
|
||||
context.Response.OutputStream.Close();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current app instance.
|
||||
/// </summary>
|
||||
public static ScratchLinkApp Current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the platform-specific services provider.
|
||||
/// This provides access to services like the session manager or GATT helpers.
|
||||
/// </summary>
|
||||
public IServiceProvider Services { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Run the app.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
if (!HttpListener.IsSupported)
|
||||
{
|
||||
// TODO: pop up an error message
|
||||
return;
|
||||
}
|
||||
|
||||
this.webSocketListener.Start(new[]
|
||||
{
|
||||
string.Format("http://127.0.0.1:{0}/", WebSocketPort),
|
||||
string.Format("http://localhost:{0}/", WebSocketPort),
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleSessionDebug(WebSocketContext context)
|
||||
{
|
||||
var origin = context.Headers.Get("origin");
|
||||
var socket = context.WebSocket;
|
||||
Debug.Print("New connection");
|
||||
socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Scratch Link app instance.
|
||||
/// Fills the role of the .NET generic host or <c>MauiAppBuilder</c>.
|
||||
/// </summary>
|
||||
public class Builder
|
||||
{
|
||||
private string[] arguments;
|
||||
private Type sessionManagerType;
|
||||
private Type gattHelpersBaseType;
|
||||
private Type gattHelpersType;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the arguments which will be passed to the app host.
|
||||
/// Must be called before Build().
|
||||
/// </summary>
|
||||
/// <param name="arguments">Command line arguments from app invocation.</param>
|
||||
public void SetArguments(string[] arguments)
|
||||
{
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the type which will be used to build the platform-specific session manager.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSessionManager">The platform-specific session manager type.</typeparam>
|
||||
internal void SetSessionManager<TSessionManager>()
|
||||
where TSessionManager : SessionManager
|
||||
{
|
||||
this.sessionManagerType = typeof(TSessionManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the types which will be used to build the BLE GATT helpers.
|
||||
/// </summary>
|
||||
/// <typeparam name="TGattHelpers">The platform-specific GATT helpers type.</typeparam>
|
||||
/// <typeparam name="TUUID">The platform-specific type for BLE UUID values.</typeparam>
|
||||
internal void SetGattHelpers<TGattHelpers, TUUID>()
|
||||
where TGattHelpers : GattHelpers<TUUID>
|
||||
{
|
||||
this.gattHelpersBaseType = typeof(GattHelpers<TUUID>);
|
||||
this.gattHelpersType = typeof(TGattHelpers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Scratch Link app host.
|
||||
/// </summary>
|
||||
/// <returns>A new Scratch Link app host.</returns>
|
||||
internal ScratchLinkApp Build()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
var serviceProviderOptions = new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true };
|
||||
var servicesProvider = new DefaultServiceProviderFactory(serviceProviderOptions)
|
||||
.CreateBuilder(serviceCollection)
|
||||
.AddSingleton(typeof(SessionManager), this.sessionManagerType)
|
||||
.AddSingleton(this.gattHelpersBaseType, this.gattHelpersType)
|
||||
.BuildServiceProvider();
|
||||
return new ScratchLinkApp(servicesProvider);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,5 +27,6 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Session.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SessionManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)WebSocketListener.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ScratchLinkApp.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
namespace ScratchLink.Mac;
|
||||
|
||||
using AppKit;
|
||||
using CoreBluetooth;
|
||||
using Foundation;
|
||||
using ScratchLink.Mac.BLE;
|
||||
|
||||
/// <summary>
|
||||
/// Scratch Link's implementation of the NSApplicationDelegate protocol.
|
||||
|
@ -19,7 +21,14 @@ public class AppDelegate : NSApplicationDelegate
|
|||
/// <param name="notification">A notification named <c>didFinishLaunchingNotification</c>.</param>
|
||||
public override void DidFinishLaunching(NSNotification notification)
|
||||
{
|
||||
// Insert code here to initialize your application
|
||||
var appBuilder = new ScratchLinkApp.Builder();
|
||||
appBuilder.SetArguments(new NSProcessInfo().Arguments);
|
||||
appBuilder.SetSessionManager<MacSessionManager>();
|
||||
appBuilder.SetGattHelpers<MacGattHelpers, CBUUID>();
|
||||
|
||||
var app = appBuilder.Build();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -105,6 +105,9 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="$(SolutionDir)stylecop.json" />
|
||||
<PackageReference Include="Fleck">
|
||||
<Version>1.2.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json">
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:ScratchLink"
|
||||
x:Class="ScratchLink.ScratchLinkApp">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<Color x:Key="PrimaryColor">#512bdf</Color>
|
||||
<Color x:Key="SecondaryColor">White</Color>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="TextColor" Value="{DynamicResource PrimaryColor}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="TextColor" Value="{DynamicResource SecondaryColor}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="BackgroundColor" Value="{DynamicResource PrimaryColor}" />
|
||||
<Setter Property="Padding" Value="14,10" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -1,72 +0,0 @@
|
|||
// <copyright file="ScratchLinkApp.xaml.cs" company="Scratch Foundation">
|
||||
// Copyright (c) Scratch Foundation. All rights reserved.
|
||||
// </copyright>
|
||||
|
||||
namespace ScratchLink;
|
||||
|
||||
using ScratchLink.Resources.Strings;
|
||||
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ScratchLinkApp"/> class contains the cross-platform entry point for the application.
|
||||
/// </summary>
|
||||
public partial class ScratchLinkApp : Application
|
||||
{
|
||||
private const int WebSocketPort = 20111;
|
||||
|
||||
private readonly SessionManager sessionManager;
|
||||
private readonly WebSocketListener webSocketListener;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScratchLinkApp"/> class.
|
||||
/// This is the cross-platform entry point.
|
||||
/// </summary>
|
||||
public ScratchLinkApp()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
this.MainPage = new MainPage();
|
||||
|
||||
if (!HttpListener.IsSupported)
|
||||
{
|
||||
// TODO: this doesn't work right
|
||||
this.MainPage.DisplayAlert("Error", AppResource.HTTP_Listener_not_supported, "Quit").ContinueWith(task =>
|
||||
{
|
||||
this.Quit();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.sessionManager = IPlatformApplication.Current.Services.GetService<SessionManager>();
|
||||
|
||||
this.webSocketListener = new ()
|
||||
{
|
||||
OnWebSocketConnection = (webSocketContext) =>
|
||||
{
|
||||
this.sessionManager.ClientDidConnect(webSocketContext);
|
||||
},
|
||||
OnOtherConnection = (context) =>
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
},
|
||||
};
|
||||
this.webSocketListener.Start(new[]
|
||||
{
|
||||
string.Format("http://127.0.0.1:{0}/", WebSocketPort),
|
||||
string.Format("http://localhost:{0}/", WebSocketPort),
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleSessionDebug(WebSocketContext context)
|
||||
{
|
||||
var origin = context.Headers.Get("origin");
|
||||
var socket = context.WebSocket;
|
||||
this.Dispatcher.Dispatch(() =>
|
||||
{
|
||||
this.MainPage.DisplayAlert("New connection", string.Format("Path: {0}\nOrigin: {1}", context.RequestUri.AbsolutePath, origin), "OK");
|
||||
});
|
||||
socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue