shinyorg / shiny

.NET Framework for Backgrounding & Device Hardware Services (iOS, Android, & Catalyst)

Home Page:https://shinylib.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: PushManager never returns a value from the RequestAccess task.

HollowAccount opened this issue · comments

Component/Nuget

Push - Native (Shiny.Push)

What operating system(s) are effected?

  • iOS (13+ supported)
  • Mac Catalyst
  • Android (8+ supported)

Version(s) of Operation Systems

iOS 17.2 (physical device) and others

Hosting Model

  • MAUI
  • Native/Classic Xamarin
  • Manual

Steps To Reproduce

  1. Include nugets: Shiny.Core, Shiny.Push, Shiny.Hosting.Maui
  2. Configure the project correctly to use push notifications
  3. Requests platform permission to send push notifications using method RequestAccess from PushManager.

Android: works fine
iOS: never returns the task result and hangs

Expected Behavior

The RequestAccess method should return a value.

Actual Behavior

When the RequestAccess method is called, nothing happens and the tank result is never returned.

In AppDelegate for RegisteredForRemoteNotifications I get the appropriate deviceToken which is then passed to OnRegisteredForRemoteNotifications():

 [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
 public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) => Shiny.Hosting.Host.Lifecycle.OnRegisteredForRemoteNotifications(deviceToken);

I don't know why, but this PushManager method is not called:

public void OnRegistered(NSData deviceToken) => this.tokenSource?.TrySetResult(deviceToken);

As a result, in the RequestRawToken method we wait forever for the result from the TaskCompletionSource tokenSource.

  protected async Task<NSData> RequestRawToken(CancellationToken cancelToken)
  {
      this.tokenSource = new();
      using var cancelSrc = cancelToken.Register(() => this.tokenSource.TrySetCanceled());

      await this.platform
          .InvokeOnMainThreadAsync(
              () => UIApplication
                  .SharedApplication
                  .RegisterForRemoteNotifications()
          )
          .ConfigureAwait(false);

      var rawToken = await this.tokenSource.Task.ConfigureAwait(false);
      return rawToken;
  }

It looks as if PushManager is not a singleton at all or the OnRegisteredForRemoteNotifications method uses a different PushManager instance than the one registered for the application.

Exception or Log output

No response

Code Sample

No response

Code of Conduct

  • I have supplied a reproducible sample that is NOT FROM THE SHINY SAMPLES!
  • I am a Sponsor OR I am using the LATEST stable/beta version from nuget (v3.0 stable - ALPHAS are not taking issues - Sponsors can still send v2 issues)
  • I am Sponsor OR My GitHub account is 30+ days old
  • I understand that if I am checking these boxes and I am not actually following what they are saying, I will be removed from this repository!

You need to provide a full sample showing the issue

3.3.3 has been tested here: https://github.com/shinyorg/pushtester and verified to be working

@aritchie

3.3.3 has been tested here: https://github.com/shinyorg/pushtester and verified to be working

This sample repository additionally uses Shiny.Push.FirebaseMessaging and Shiny.Push.AzureNotificationHubs
I am not analyzing the use of these additional libs.

My solution is based on a "basic" implementation:
image

I forked the source code of the entire library to analyze why it doesn't work. And as I suspected, there are multiple instances registered for PushManager:

image

In the case of iOS, this leads to an error because we call the RequestAccess method from the IPushManager interface, while IosLifecycleExecutor uses an instance resolved from the IIosLifecycle.IRemoteNotifications interface.
image

To show what the problem is, I added the "Guid" identifier to the PushManager service. Value at the time of executing the RequestAccess method:
image

Here is another instance with a different identifier on which operations are performed in IosLifecycleExecutor.
image

For Android it does not matter, but in the case of iOS we wait for the result for the TaskCompletionSource which is never completed because SetResult is made for a completely different instance than we expect.

This sample repository additionally uses Shiny.Push.FirebaseMessaging and Shiny.Push.AzureNotificationHubs
I am not analyzing the use of these additional libs

These are built on top of the "basic" implementation

For Android it does not matter, but in the case of iOS we wait for the result for the TaskCompletionSource which is never completed because SetResult is made for a completely different instance than we expect.

If you want me to look at this, you have to supply a reproducible sample or this will be locked.