Skip to content

Commit

Permalink
feat(ProtocolActivation): Implemented protocol activation on WASM
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed May 18, 2020
1 parent f6576ba commit 1211d49
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 10 deletions.
23 changes: 23 additions & 0 deletions doc/articles/features/protocol-activation.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ Register your protocol on the `MainActivity` with the `[IntentFilter]` attribute

Note both `CategoryDefault` and `CategoryBrowsable` should be listed.

### WASM

WASM implementation uses the [`Navigator.registerProtocolHandler` API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler). This has several limitations for the custom scheme:

- The custom scheme's name must begin with `web+`
- The custom scheme's name must include at least 1 letter after the `web+` prefix
- The custom scheme must have only lowercase ASCII letters in its name.

To register the custom theme, call the WASM specific `Uno.Helpers.ProtocolActivation` API when appropriate to let the user confirm URI handler association:

```
#if __WASM__
Uno.Helpers.ProtocolActivation.RegisterCustomScheme(
"web+myscheme",
new System.Uri("http://localhost:55838/"),
"Can we handle web+myscheme links?");
```

The first argument is the scheme name, the second is the base URL of your application (it must match the current domain to be registered successfully), and the third is a text prompt, which will be displayed to the user to ask for permission.

When a link with the custom scheme gets executed, the browser will navigate to a your URL with additional `unoprotocolactivation` query string key, which will contain the custom URI. Uno internally recognizes this query string key and executes `OnActivated` appropriately.


### UWP

Works according to Windows docs, see [Microsoft Docs](https://docs.microsoft.com/en-us/windows/uwp/launch-resume/handle-uri-activation)
Expand Down
28 changes: 28 additions & 0 deletions src/Uno.UI/UI/Xaml/Application.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using System.Threading;
using Uno.UI;
using Uno.UI.Xaml;
using System.Web;
using System.Collections.Specialized;

namespace Windows.UI.Xaml
{
Expand Down Expand Up @@ -65,6 +67,32 @@ private void Initialize()
this.Log().Debug("Launch arguments: " + arguments);
}
InitializationCompleted();

if (!string.IsNullOrEmpty(arguments))
{
NameValueCollection queryValues = null;
try
{
queryValues = HttpUtility.ParseQueryString(arguments);
}
catch (Exception ex)
{
this.Log().Error(
"Launch arguments could not be parsed as a query string", ex);
}
if (queryValues != null)
{
if (queryValues[Uno.Helpers.ProtocolActivation.QueryKey] is
string protocolUriString)
{
protocolUriString = Uri.UnescapeDataString(protocolUriString);
var uri = new Uri(protocolUriString);
OnActivated(new ProtocolActivatedEventArgs(uri, ApplicationExecutionState.NotRunning));
return;
}
}
}

OnLaunched(new LaunchActivatedEventArgs(ActivationKind.Launch, arguments));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if __IOS__ || __ANDROID__
#if __IOS__ || __ANDROID__ || __WASM__
using System;

namespace Windows.ApplicationModel.Activation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if __IOS__ || __ANDROID__
#if __IOS__ || __ANDROID__ || __WASM__
using System;

namespace Windows.ApplicationModel.Activation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.ApplicationModel.Activation
{
#if false || false || NET461 || __WASM__ || __MACOS__
#if false || false || NET461 || false || __MACOS__
[global::Uno.NotImplemented]
#endif
public partial interface IProtocolActivatedEventArgs : global::Windows.ApplicationModel.Activation.IActivatedEventArgs
{
#if false || false || NET461 || __WASM__ || __MACOS__
#if false || false || NET461 || false || __MACOS__
global::System.Uri Uri
{
get;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
#pragma warning disable 114 // new keyword hiding
namespace Windows.ApplicationModel.Activation
{
#if false || false || NET461 || __WASM__ || __MACOS__
#if false || false || NET461 || false || __MACOS__
[global::Uno.NotImplemented]
#endif
public partial class ProtocolActivatedEventArgs : global::Windows.ApplicationModel.Activation.IProtocolActivatedEventArgs,global::Windows.ApplicationModel.Activation.IActivatedEventArgs,global::Windows.ApplicationModel.Activation.IProtocolActivatedEventArgsWithCallerPackageFamilyNameAndData,global::Windows.ApplicationModel.Activation.IApplicationViewActivatedEventArgs,global::Windows.ApplicationModel.Activation.IViewSwitcherProvider,global::Windows.ApplicationModel.Activation.IActivatedEventArgsWithUser
#endif
public partial class ProtocolActivatedEventArgs : global::Windows.ApplicationModel.Activation.IProtocolActivatedEventArgs,global::Windows.ApplicationModel.Activation.IActivatedEventArgs,global::Windows.ApplicationModel.Activation.IProtocolActivatedEventArgsWithCallerPackageFamilyNameAndData,global::Windows.ApplicationModel.Activation.IApplicationViewActivatedEventArgs,global::Windows.ApplicationModel.Activation.IViewSwitcherProvider,global::Windows.ApplicationModel.Activation.IActivatedEventArgsWithUser
{
#if false || false || NET461 || __WASM__ || __MACOS__
#if false || false || NET461 || false || __MACOS__
[global::Uno.NotImplemented]
public global::Windows.ApplicationModel.Activation.ActivationKind Kind
{
Expand All @@ -17,7 +17,7 @@ public partial class ProtocolActivatedEventArgs : global::Windows.ApplicationMo
}
}
#endif
#if false || false || NET461 || __WASM__ || __MACOS__
#if false || false || NET461 || false || __MACOS__
[global::Uno.NotImplemented]
public global::Windows.ApplicationModel.Activation.ApplicationExecutionState PreviousExecutionState
{
Expand Down Expand Up @@ -57,7 +57,7 @@ public int CurrentlyShownApplicationViewId
}
}
#endif
#if false || false || NET461 || __WASM__ || __MACOS__
#if false || false || NET461 || false || __MACOS__
[global::Uno.NotImplemented]
public global::System.Uri Uri
{
Expand Down
74 changes: 74 additions & 0 deletions src/Uno.UWP/Helpers/ProtocolActivation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#if __WASM__
using System;
using System.Linq;
using System.Web;

namespace Uno.Helpers
{
public static class ProtocolActivation
{
internal const string QueryKey = "unoprotocolactivation";

/// <summary>
/// Registers a custom URI scheme for protocol activation on WASM.
/// </summary>
/// <param name="scheme">Scheme (must start with web+, after which must follow one or more lowercase ASCII letter).</param>
/// <param name="domain">Domain on which your application is running.</param>
/// <param name="prompt">Prompt to show to the user.</param>
public static void RegisterCustomScheme(string scheme, Uri domain, string prompt)
{
// rules as per https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler

// The custom scheme's name begins with web+
if (scheme.IndexOf("web+", System.StringComparison.InvariantCulture) != 0)
{
throw new ArgumentException(
"Scheme must start with 'web+'",
nameof(scheme));
}

var schemeWithoutPrefix = scheme.Substring("web+".Length);
// The custom scheme's name includes at least 1 letter after the web+ prefix
if (schemeWithoutPrefix.Length == 0)
{
throw new ArgumentException(
"Scheme must include at least 1 letter after 'web+' prefix",
nameof(scheme));
}

// The custom scheme has only lowercase ASCII letters in its name.
if (!schemeWithoutPrefix.ToCharArray().All(c => 'a' <= c && c <= 'z'))
{
throw new ArgumentException(
"Scheme must include only lowercase ASCII letters after " +
"the 'web+' prefix",
nameof(scheme));
}

if (domain == null)
{
throw new ArgumentNullException(nameof(domain));
}

if (!domain.IsAbsoluteUri)
{
throw new ArgumentException(
"Domain name must be an absolute URI.",
nameof(domain));
}

var uriBuilder = new UriBuilder(domain);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query[QueryKey] = ""; //set empty, otherwise %s would be encoded
uriBuilder.Query = query.ToString();
var uriString = uriBuilder.ToString();

uriString += "%s";

// register scheme
var initialized = Uno.Foundation.WebAssemblyRuntime.InvokeJS(
$"navigator.registerProtocolHandler('{scheme}', '{uriString}' , '{prompt.Replace("'", "\\'")}')");
}
}
}
#endif

0 comments on commit 1211d49

Please sign in to comment.