amplify-flutter icon indicating copy to clipboard operation
amplify-flutter copied to clipboard

amplify_auth_cognito - Intent crash with wc: type link - "This isn't a hierarchical URI."

Open rhamnett opened this issue 11 months ago • 18 comments

Description

Trying to open a wallet connect URL from the browser but getting an error when AmplifyAuthCognitoPlugin intercepts the intent.

wc:1de1069ddb4fb3406f82b7fe2dacb8dc487cc617c4cf48934867d372eaf7b460@2?relay-protocol=irn&symKey=6312c03106b30ffe780e97518d0f52b0d3724250918fa4ace0a0df38596963c7

D/AmplifyAuthCognitoPlugin(25255): [onNewIntent] Got intent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=wc:89053697f1e9a987941cd8ace039487fc0f1e7d9fdfcae6c9c17458cd1f23632@2?relay-protocol=irn&symKey=d1276382fb659f765eee9313c68bfb79ea16a6cc79f67bcace4877692c910d5a flg=0x14400000 cmp=app.m1nty.android/.MainActivity (has extras) }
D/AndroidRuntime(25255): Shutting down VM
E/AndroidRuntime(25255): FATAL EXCEPTION: main
E/AndroidRuntime(25255): Process: app.m1nty.android, PID: 25255
E/AndroidRuntime(25255): java.lang.UnsupportedOperationException: This isn't a hierarchical URI.
E/AndroidRuntime(25255): 	at android.net.Uri.getQueryParameterNames(Uri.java:1616)
E/AndroidRuntime(25255): 	at com.amazonaws.amplify.amplify_auth_cognito.AmplifyAuthCognitoPluginKt.getQueryParameters(AmplifyAuthCognitoPlugin.kt:546)
E/AndroidRuntime(25255): 	at com.amazonaws.amplify.amplify_auth_cognito.AmplifyAuthCognitoPlugin.onNewIntent(AmplifyAuthCognitoPlugin.kt:468)
E/AndroidRuntime(25255): 	at io.flutter.embedding.engine.FlutterEngineConnectionRegistry$FlutterEngineActivityPluginBinding.onNewIntent(FlutterEngineConnectionRegistry.java:838)
E/AndroidRuntime(25255): 	at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.onNewIntent(FlutterEngineConnectionRegistry.java:450)
E/AndroidRuntime(25255): 	at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onNewIntent(FlutterActivityAndFragmentDelegate.java:838)
E/AndroidRuntime(25255): 	at io.flutter.embedding.android.FlutterFragment.onNewIntent(FlutterFragment.java:1219)
E/AndroidRuntime(25255): 	at io.flutter.embedding.android.FlutterFragmentActivity.onNewIntent(FlutterFragmentActivity.java:660)
E/AndroidRuntime(25255): 	at android.app.Activity.performNewIntent(Activity.java:8153)
E/AndroidRuntime(25255): 	at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1429)
E/AndroidRuntime(25255): 	at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1442)
E/AndroidRuntime(25255): 	at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:3885)
E/AndroidRuntime(25255): 	at android.app.ActivityThread.handleNewIntent(ActivityThread.java:3892)
E/AndroidRuntime(25255): 	at android.app.servertransaction.NewIntentItem.execute(NewIntentItem.java:56)
E/AndroidRuntime(25255): 	at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
E/AndroidRuntime(25255): 	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:136)
E/AndroidRuntime(25255): 	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
E/AndroidRuntime(25255): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2257)
E/AndroidRuntime(25255): 	at android.os.Handler.dispatchMessage(Handler.java:106)
E/AndroidRuntime(25255): 	at android.os.Looper.loopOnce(Looper.java:201)
E/AndroidRuntime(25255): 	at android.os.Looper.loop(Looper.java:288)
E/AndroidRuntime(25255): 	at android.app.ActivityThread.main(ActivityThread.java:7953)
E/AndroidRuntime(25255): 	at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(25255): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:553)
E/AndroidRuntime(25255): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
I/Process (25255): Sending signal. PID: 25255 SIG: 9

Intent config:

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="wc" />
            </intent-filter>

Categories

  • [ ] Analytics
  • [ ] API (REST)
  • [ ] API (GraphQL)
  • [X] Auth
  • [ ] Authenticator
  • [ ] DataStore
  • [ ] Notifications (Push)
  • [ ] Storage

Steps to Reproduce

No response

Screenshots

No response

Platforms

  • [ ] iOS
  • [X] Android
  • [ ] Web
  • [ ] macOS
  • [ ] Windows
  • [ ] Linux

Flutter Version

3.13.9

Amplify Flutter Version

1.7.0

Deployment Method

Amplify CLI

Schema

No response

rhamnett avatar Mar 06 '24 20:03 rhamnett

Hi @rhamnett, can you please provide more context and reproduction steps so we can better assist you?

Equartey avatar Mar 07 '24 20:03 Equartey

Hello thanks for the reply.

The problem is that there are special wallet connect (wc: ) type URLs, like in the initial post. Cognito plugin seems to be trying to parse these when opening them from the Web via an intent. It should just have a check to ensure that the Uri is a type that can be parsed. I have again included a stack trace for you.

If you want to recreate - have a plain project with amplify auth, then register the intent from the initial post.

You can go to opesea.com - click login, then click wallet connect, find "m1nty" wallet, then click to open the app via the intent.

Heres the culprit code in com.amazonaws.amplify.amplify_auth_cognito.AmplifyAuthCognitoPluginKt.getQueryParameters(AmplifyAuthCognitoPlugin.kt:546)

val Uri.queryParameters: MutableMap<String, String>
  get() {
    val queryParameters = mutableMapOf<String, String>()
    for (name in queryParameterNames) {
      queryParameters[name] = getQueryParameter(name) ?: ""
    }
    return queryParameters
  }

Causes

    Caused by: java.lang.UnsupportedOperationException: This isn't a hierarchical URI.
                                                                                                    	at android.net.Uri.getQueryParameterNames(Uri.java:1614)

Stack trace....

2024-03-07 22:44:08.884 13930-13930 AmplifyAut...nitoPlugin app.m1nty.android                    D  [onNewIntent] Got intent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=wc: flg=0x14400000 cmp=app.m1nty.android/.MainActivity (has extras) }
2024-03-07 22:44:08.886 14163-14163 cessService0:36         pid-14163                            E  Not starting debugger since process cannot load the jdwp agent.
2024-03-07 22:44:08.886 13930-13930 AndroidRuntime          app.m1nty.android                    D  Shutting down VM
2024-03-07 22:44:08.896 13930-13930 AndroidRuntime          app.m1nty.android                    E  FATAL EXCEPTION: main
                                                                                                    Process: app.m1nty.android, PID: 13930
                                                                                                    java.lang.UnsupportedOperationException: This isn't a hierarchical URI.
                                                                                                    	at android.net.Uri.getQueryParameterNames(Uri.java:1614)
                                                                                                    	at com.amazonaws.amplify.amplify_auth_cognito.AmplifyAuthCognitoPluginKt.getQueryParameters(AmplifyAuthCognitoPlugin.kt:546)
                                                                                                    	at com.amazonaws.amplify.amplify_auth_cognito.AmplifyAuthCognitoPlugin.onNewIntent(AmplifyAuthCognitoPlugin.kt:468)
                                                                                                    	at io.flutter.embedding.engine.FlutterEngineConnectionRegistry$FlutterEngineActivityPluginBinding.onNewIntent(FlutterEngineConnectionRegistry.java:838)
                                                                                                    	at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.onNewIntent(FlutterEngineConnectionRegistry.java:450)
                                                                                                    	at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onNewIntent(FlutterActivityAndFragmentDelegate.java:838)
                                                                                                    	at io.flutter.embedding.android.FlutterFragment.onNewIntent(FlutterFragment.java:1219)
                                                                                                    	at io.flutter.embedding.android.FlutterFragmentActivity.onNewIntent(FlutterFragmentActivity.java:660)
                                                                                                    	at android.app.Activity.performNewIntent(Activity.java:8613)
                                                                                                    	at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1556)
                                                                                                    	at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1569)
                                                                                                    	at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:3962)
                                                                                                    	at android.app.ActivityThread.handleNewIntent(ActivityThread.java:3969)
                                                                                                    	at android.app.servertransaction.NewIntentItem.execute(NewIntentItem.java:56)
                                                                                                    	at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
                                                                                                    	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
                                                                                                    	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
                                                                                                    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:106)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:205)
                                                                                                    	at android.os.Looper.loop(Looper.java:294)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:8177)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
I/FA      (20125): Application backgrounded at: timestamp_millis: 1709917536352
D/com.llfbandit.app_links(20125): Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=wc: flg=0x14400000 cmp=app.m1nty.android/.MainActivity (has extras) }
D/com.llfbandit.app_links(20125): handleIntent: (Action) android.intent.action.VIEW
D/com.llfbandit.app_links(20125): handleIntent: (Data) wc:b96fafd049f6b75f2e7755b693589f2d36733495f03244dcb81945091613394f@2?relay-protocol=irn&symKey=0f2f13318d31828764b6bc30851a22f5a763aeb2cc90ada177d9ecf5d1d18362
D/AmplifyAuthCognitoPlugin(20125): [onNewIntent] Got intent: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=wc: flg=0x14400000 cmp=app.m1nty.android/.MainActivity (has extras) }

rhamnett avatar Mar 07 '24 23:03 rhamnett

Hi @rhamnett, thanks for the extra info. I've been able to reproduce this and I'm looking into a fix.

We will provide an update here when we can, thanks.

Equartey avatar Mar 13 '24 19:03 Equartey

Ah brilliant. Thanks so much for taking the time to look into this. Really appreciate it

rhamnett avatar Mar 13 '24 19:03 rhamnett

@rhamnett no problem.

I opened a PR that at least prevents the error, although not sure this will be the fix we end up using.

Can you give it a try and see if it unblocks you?

Add this to your pubspec.yaml:

dependency_overrides:
  amplify_auth_cognito:
    git:
      url: https://github.com/aws-amplify/amplify-flutter.git
      ref: 8d8bc637502e0837a518d74b7700bf3ee61d6afd
      path: packages/auth/amplify_auth_cognito

Equartey avatar Mar 13 '24 20:03 Equartey

Will be my pleasure. I'll try tomorrow and feed back.

rhamnett avatar Mar 13 '24 20:03 rhamnett

@Equartey just tried this, sorry.....please can you help me understand what versions I'll need to make this override work?

Running Gradle task 'assembleDebug'...
Warning: unexpected element (uri:"", local:"extension-level"). Expected elements are <{}codename>,<{}layoutlib>,<{}api-level>
Warning: unexpected element (uri:"", local:"base-extension"). Expected elements are <{}codename>,<{}layoutlib>,<{}api-level>
Warning: unexpected element (uri:"", local:"base-extension"). Expected elements are <{}codename>,<{}layoutlib>,<{}api-level>
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_impl.dart:66:10: Error: Type 'LegacyDeviceDetails' not found.
  Future<LegacyDeviceDetails?> fetchLegacyDeviceSecrets({
         ^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_impl.dart:71:23: Error: The method 'fetchLegacyDeviceSecrets' isn't defined for the class 'LegacyCredentialProvider'.
 - 'LegacyCredentialProvider' is from 'package:amplify_auth_cognito_dart/src/credentials/legacy_credential_provider.dart' ('../../.pub-cache/hosted/pub.dev/amplify_auth_cognito_dart-0.10.10/lib/src/credentials/legacy_credential_provider.dart').
Try correcting the name to the name of an existing method, or defining a method named 'fetchLegacyDeviceSecrets'.
    return _instance!.fetchLegacyDeviceSecrets(
                      ^^^^^^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_impl.dart:83:23: Error: The method 'deleteLegacyDeviceSecrets' isn't defined for the class 'LegacyCredentialProvider'.
 - 'LegacyCredentialProvider' is from 'package:amplify_auth_cognito_dart/src/credentials/legacy_credential_provider.dart' ('../../.pub-cache/hosted/pub.dev/amplify_auth_cognito_dart-0.10.10/lib/src/credentials/legacy_credential_provider.dart').
Try correcting the name to the name of an existing method, or defining a method named 'deleteLegacyDeviceSecrets'.
    return _instance!.deleteLegacyDeviceSecrets(
                      ^^^^^^^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_ios.dart:165:10: Error: Type 'LegacyDeviceDetails' not found.
  Future<LegacyDeviceDetails?> fetchLegacyDeviceSecrets({
         ^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_ios.dart:195:12: Error: The method 'LegacyDeviceDetails' isn't defined for the class 'LegacyCredentialProviderIOS'.
 - 'LegacyCredentialProviderIOS' is from 'package:amplify_auth_cognito/src/credentials/legacy_credential_provider_ios.dart' ('../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_ios.dart').
Try correcting the name to the name of an existing method, or defining a method named 'LegacyDeviceDetails'.
    return LegacyDeviceDetails(
           ^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_credential_provider_android.dart:52:10: Error: Type 'LegacyDeviceDetails' not found.
  Future<LegacyDeviceDetails?> fetchLegacyDeviceSecrets({
         ^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_device_details_extension.dart:15:3: Error: Type 'LegacyDeviceDetails' not found.
  LegacyDeviceDetails? toLegacyDeviceDetails() {
  ^^^^^^^^^^^^^^^^^^^
../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/credentials/legacy_device_details_extension.dart:16:12: Error: The method 'LegacyDeviceDetails' isn't defined for the class 'LegacyDeviceDetailsSecret'.
 - 'LegacyDeviceDetailsSecret' is from 'package:amplify_auth_cognito/src/native_auth_plugin.g.dart' ('../../.pub-cache/git/amplify-flutter-8d8bc637502e0837a518d74b7700bf3ee61d6afd/packages/auth/amplify_auth_cognito/lib/src/native_auth_plugin.g.dart').
Try correcting the name to the name of an existing method, or defining a method named 'LegacyDeviceDetails'.
    return LegacyDeviceDetails(
           ^^^^^^^^^^^^^^^^^^^
Target kernel_snapshot failed: Exception


FAILURE: Build failed with an exception.

* Where:
Script '/Users/rick/flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy' line: 1297

* What went wrong:
Execution failed for task ':app:compileFlutterBuildDebug'.
> Process 'command '/Users/rick/flutter/bin/flutter'' finished with non-zero exit value 1

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 8s
Exception: Gradle task assembleDebug failed with exit code 1

pubspec.yaml:

version: 1.0.8+141

environment:
  sdk: ">=3.0.0 <4.0.0"

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers  below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
#  amplify_auth_cognito: ^1.7.0
  amplify_flutter: ^1.7.0
  cached_network_image: ^3.3.0
  cloud_kit: ^1.2.0
  cupertino_icons: ^1.0.2
  eth_sig_util: ^0.0.8
  ethers: ^0.0.1+3
  firebase_analytics: ^10.6.4
  firebase_app_check: ^0.2.1+1
  firebase_core: ^2.19.0
  firebase_crashlytics: ^3.4.4
  firebase_messaging: ^14.7.4
  firebase_remote_config: ^4.3.4
  firebase_auth: ^4.13.0
  google_sign_in: ^6.1.6
  cloud_firestore: ^4.14.0
  flutter:
    sdk: flutter
  flutter_markdown: ^0.6.18
  flutter_secure_storage: ^9.0.0
  http: ^0.13.5
  is_first_run: ^1.0.0
  path: null
  path_provider: null
  share_plus: ^7.2.2
  app_links: ^3.4.5
  url_launcher: ^6.1.4
  sensors: ^2.0.3
  qr_flutter: ^4.0.0
  rate_my_app: ^2.0.0
  local_auth: ^2.1.6
  web3dart: ^2.5.0
  webview_flutter: ^4.2.2
  wallet_connect_v2: 1.0.8
  qr_code_scanner: ^1.0.1
  shared_preferences: ^2.2.2
#  flutter_wallet_card: ^3.0.3
  get_it: ^7.6.4
  connectivity_plus: ^5.0.2

flutter_icons:
  android: "launcher_icon"
#  android: false
  ios: true

  image_path: "assets/icon/logo512.png"
  min_sdk_android: 21 # android min sdk min:16, default 21
  remove_alpha_ios: true
#  adaptive_icon_background: "#161616"

dev_dependencies:
  flutter_launcher_icons: ^0.13.1
  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.1
  flutter_test:
    sdk: flutter
  logging: ^1.1.0

dependency_overrides:
  amplify_auth_cognito:
    git:
      url: https://github.com/aws-amplify/amplify-flutter.git
      ref: 8d8bc637502e0837a518d74b7700bf3ee61d6afd
      path: packages/auth/amplify_auth_cognito


rhamnett avatar Mar 13 '24 21:03 rhamnett

@rhamnett hm versions look good. Try flutter pub clean & flutter pub get. Does that give any errors?

Equartey avatar Mar 13 '24 21:03 Equartey

Tried that I'm afraid, same error @Equartey

flutter clean flutter pub get

(flutter pub clean - is not a command)

rhamnett avatar Mar 13 '24 21:03 rhamnett

@rhamnett oh sorry about the typo.

Not sure what the issue is yet, I'm not seeing that on my end.

I'll give you an update when I have one.

Equartey avatar Mar 13 '24 21:03 Equartey

@Equartey no problem, could if be that your patch is from 1.7.0 not 1.7.1?

! amplify_auth_cognito 1.7.0 from git https://github.com/aws-amplify/amplify-flutter.git at 8d8bc6 in packages/auth/amplify_auth_cognito (overridden)

rhamnett avatar Mar 13 '24 21:03 rhamnett

Hi @rhamnett, I've made an update to my branch. Can you retry using this updated pubspec override?

dependency_overrides:
  amplify_auth_cognito:
    git:
      url: https://github.com/aws-amplify/amplify-flutter.git
      ref: fix/auth-android-intent-uri-query-parsing
      path: packages/auth/amplify_auth_cognito
  amplify_auth_cognito_dart:
    git:
      url: https://github.com/aws-amplify/amplify-flutter.git
      ref: fix/auth-android-intent-uri-query-parsing
      path: packages/auth/amplify_auth_cognito_dart

Equartey avatar Mar 14 '24 15:03 Equartey

@Equartey thanks for the continued help, after a flutter clean :

Error detected in pubspec.yaml:
No file or variants found for asset: packages/amplify_auth_cognito_dart/lib/src/workers/workers.min.js.

This asset was included from package amplify_auth_cognito_dart.
Target debug_android_application failed: Exception: Failed to bundle asset files.

rhamnett avatar Mar 14 '24 15:03 rhamnett

@rhamnett hmm, can you try without the amplify_auth_cognito_dart override?

Equartey avatar Mar 14 '24 16:03 Equartey

@Equartey That's worked and I believe it's solved the original query params issue!

rhamnett avatar Mar 14 '24 17:03 rhamnett

@rhamnett awesome, happy to hear.

Like I mentioned before, our approach may change, but we will update you here when its released.

Equartey avatar Mar 14 '24 17:03 Equartey

Hello just wondering if this got merged at any point or if I should keep my override. Thank you

rhamnett avatar Apr 18 '24 18:04 rhamnett

@rhamnett sorry for the delay. It has been merged and will be part of the next release. We'll update you here when that is available!

Equartey avatar Apr 25 '24 20:04 Equartey

This issue was addressed in Amplify Flutter v2 which was released a couple weeks ago. There were some breaking changes in this version. Please see the upgrade guide for more info.

Jordan-Nelson avatar May 21 '24 18:05 Jordan-Nelson