OpenNative / open-native

Open Native brings cross-platform communities together to help them collaborate and strengthen each other through development diversity.
https://open-native.org/
MIT License
449 stars 7 forks source link

Issue with react-native-app-auth #23

Closed fpaaske closed 1 year ago

fpaaske commented 1 year ago

As discussed on Discord I'm having issues with react-native-app-auth. After setting up open-native and adding react-native-app-auth, I get the following error on Android when calling authorize():

  JS: CONSOLE LOG: Error: ����
  JS:     at file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:278:0
  JS:     at new ZoneAwarePromise (file: src/webpack:/ns-ng-open-native/node_modules/zone.js/fesm2015/zone.js:1411:0)
  JS:     at promisify (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:276:0)
  JS:     at <computed> [as authorize] (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/nativemodules.js:87:36)
  JS:     at authorize (file: src/webpack:/ns-ng-open-native/node_modules/react-native-app-auth/index.js:246:19)
  JS:     at ItemsComponent.doAuthorize (file: src/webpack:/ns-ng-open-native/src/app/item/items.component.ts:14:17)
  JS:     at ItemsComponent_Template_Button_tap_2_listener (file: src/webpack:/ns-ng-open-native/src/app/item/items.component.ts:45:88)
  JS:     at executeListenerWithErrorHandling (file: src/webpack:/ns-ng-open-native/node_modules/@angular/core/fesm2015/core.mjs:13956:0)
  JS:     at Object.wrapListenerIn_markDirtyAndPreventDefault (file:///data/data/org.nativescript.nsngopennative/files/app/...

On iOS I get this this error:

  ***** Fatal JavaScript exception - application has been terminated. *****
  NativeScript encountered a fatal error: Uncaught Error: Unexpected type 'other' at index 10 - the autolinker must have failed to parse the native module.
  at
  toNativeArguments(file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/ios/converter.js:45:22)
  at (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/ios/converter.js:206:42)

The log output when starting the app looks OK:

[@open-native/core/hooks/before-prepare.js] Autolinking React Native ios native modules...
[@open-native/core/hooks/prepare-ios.js] Unable to extract any methods from RCTBridgeModule named "EventDispatcher".
[@open-native/core/hooks/prepare-ios.js]: Writing RNPodspecs.h
[@open-native/core/hooks/prepare-ios.js]: Writing Podfile
[@open-native/core/hooks/prepare-ios.js]: Writing React-Native-Podspecs.podspec
[@open-native/core/hooks/prepare-ios.js]: Writing modulemap.json
[@open-native/core/hooks/before-prepare.js] Autolinked @open-native/core!
[@open-native/core/hooks/before-prepare.js] Autolinked react-native-app-auth!
[@open-native/core/hooks/before-prepare.js] ... Finished autolinking React Native ios native modules.

Example repo: https://github.com/fpaaske/open-native-issue-23

ammarahm-ed commented 1 year ago

Hey so I think I fixed it. install the latest version of open-native then try again. Also you need to configure custom AppDelegate to make the module work like this in your app.ts file above Application.run(...) as mentioned in the module docs https://github.com/FormidableLabs/react-native-app-auth#setup but the NativeScript way:

interface RNAppAuthAuthorizationFlowManager extends NSObjectProtocol {
  authorizationFlowManagerDelegate: RNAppAuthAuthorizationFlowManagerDelegate;
}
declare const RNAppAuthAuthorizationFlowManager: {
  prototype: RNAppAuthAuthorizationFlowManager;
};

interface RNAppAuthAuthorizationFlowManagerDelegate extends NSObjectProtocol {
  resumeExternalUserAgentFlowWithURL(url: NSURL): boolean;
}
declare const RNAppAuthAuthorizationFlowManagerDelegate: {
  prototype: RNAppAuthAuthorizationFlowManagerDelegate;
};

if (global.isIOS) {
  @NativeClass()
  @ObjCClass(UIApplicationDelegate, RNAppAuthAuthorizationFlowManager)
  class MyDelegate extends UIResponder implements UIApplicationDelegate, RNAppAuthAuthorizationFlowManager {
    authorizationFlowManagerDelegate: RNAppAuthAuthorizationFlowManagerDelegate;

    get window() {
      return Application.ios.window;
    }

    set window(window) {}

    applicationDidFinishLaunchingWithOptions(application: UIApplication, launchOptions: NSDictionary<string, any>): boolean {
      return true;
    }

    applicationDidBecomeActive(application: UIApplication): void {}

    applicationOpenURLOptions(app: UIApplication, url: NSURL, options: NSDictionary<string, any>): boolean {
      if (this.authorizationFlowManagerDelegate?.resumeExternalUserAgentFlowWithURL(url as NSURL)) {
        return true;
      }
      return RCTLinkingManager.applicationOpenURLOptions(app, url, options);
    }

    public 'setAuthorizationFlowManagerDelegate:'(delegate) {
      this.authorizationFlowManagerDelegate = delegate;
    }

    public static ObjCExposedMethods = {
      'setAuthorizationFlowManagerDelegate:': { returns: interop.types.void, params: [RNAppAuthAuthorizationFlowManagerDelegate] },
    };
  }
  Application.ios.delegate = MyDelegate;
}

Let me know how this goes. For me it's working now, obviously need to set proper real params probably. If you face another problem, post the logs here so i can debug further.

Right now I am getting this error:

***** Fatal JavaScript exception - application has been terminated. *****
  NativeScript encountered a fatal error: Uncaught Error: The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported.
  at
  (file: src/packages/core/src/ios/converter.ts:302:12)

probably because params are just dummy data.

fpaaske commented 1 year ago

Thank you, @ammarahm-ed! And thanks for providing the application delegate, I totally forgot about that.

probably because params are just dummy data.

Yes, with proper configuration, I can now log in and out using the react-native-app-auth plugin on iOS 👍

However, on Android I get errors like this when I'm not providing the (some? all?) optional parameters to authorize().

In this case I didn't set the clientSecret: Error: Argument at index 3 expected a string, but got undefined.

when I added clientSecret I got a new error for the additionalParameters: Error: Argument at index 5 expected an object value, but got undefined

The parameters are set in the react native plugin like this:

  const nativeMethodArguments = [
    issuer,
    redirectUrl,
    clientId,
    clientSecret,
    scopes,
    additionalParameters,
    serviceConfiguration,
    skipCodeExchange,
    convertTimeoutForPlatform(Platform.OS, connectionTimeoutSeconds),
  ];

  if (Platform.OS === 'android') {
    nativeMethodArguments.push(useNonce);
    nativeMethodArguments.push(usePKCE);
    nativeMethodArguments.push(clientAuthMethod);
    nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
    nativeMethodArguments.push(customHeaders);
  }

  if (Platform.OS === 'ios') {
    nativeMethodArguments.push(additionalHeaders);
    nativeMethodArguments.push(useNonce);
    nativeMethodArguments.push(usePKCE);
  }

  return RNAppAuth.authorize(...nativeMethodArguments);
ammarahm-ed commented 1 year ago

@fpaaske Are method calls with optional parameters working on iOS?

fpaaske commented 1 year ago

@ammarahm-ed Yes, on iOS I can call authorize like this, with only the essential parameters:

    authorize({
      clientId: this.clientId,
      issuer: this.issuer,
      redirectUrl: this.redirectUrl,
      scopes: this.scopes,
      useNonce: true,
      usePKCE: true,
      warmAndPrefetchChrome: false
    }).then(result => {
      console.log(result.accessToken);
      this.authorized = true;
      this.idToken = result.idToken;
    }).catch(error => {
      this.authorized = false;
      console.log(error.stack);
    });
ammarahm-ed commented 1 year ago

Okay, I will see, seems like android is treating all params as required. Will fix and update.

fpaaske commented 1 year ago

@ammarahm-ed I also updated my repo with a proper IdP-config (in the readme) so you can test with valid configuration too.

ammarahm-ed commented 1 year ago

@fpaaske Try the latest alpha, it should now allow optional params to be null/undefined on android.

fpaaske commented 1 year ago

@ammarahm-ed I've updated the repo and tested with the latest alpha and now the it seems like optional parameters are allowed, but I get this error instead when tapping "authorize":

  JS: CONSOLE LOG: Error: ����
  JS:     at file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:281:0
  JS:     at new ZoneAwarePromise (file: src/webpack:/ns-ng-open-native/node_modules/zone.js/fesm2015/zone.js:1411:0)
  JS:     at promisify (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:279:0)
  JS:     at <computed> [as authorize] (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/nativemodules.js:87:36)
  JS:     at authorize (file: src/webpack:/ns-ng-open-native/node_modules/react-native-app-auth/index.js:246:19)
  JS:     at AppAuthComponent.doAuthorize (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:19:17)
  JS:     at AppAuthComponent_Template_Button_tap_2_listener (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:55:90)
  JS:     at executeListenerWithErrorHandling (file: src/webpack:/ns-ng-open-native/node_modules/@angular/core/fesm2015/core.mjs:13956:0)
  JS:     at Object.wrapListenerIn_markDirtyAndPreventDefault (file:///data/data/org.nativescript.nsngopennative/files/...

You probably already know, but converter.js:281 is here :)

export function promisify(module, methodName, methodTypes, args) {
    return new Promise((resolve, reject) => {
        try {
            module[methodName](...toNativeArguments(methodTypes, args, resolve, reject)); // line 281
        }
        catch (e) {
            reject(e);
        }
    });
}
ammarahm-ed commented 1 year ago

@ammarahm-ed I've updated the repo and tested with the latest alpha and now the it seems like optional parameters are allowed, but I get this error instead when tapping "authorize":

  JS: CONSOLE LOG: Error: ����
  JS:     at file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:281:0
  JS:     at new ZoneAwarePromise (file: src/webpack:/ns-ng-open-native/node_modules/zone.js/fesm2015/zone.js:1411:0)
  JS:     at promisify (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:279:0)
  JS:     at <computed> [as authorize] (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/nativemodules.js:87:36)
  JS:     at authorize (file: src/webpack:/ns-ng-open-native/node_modules/react-native-app-auth/index.js:246:19)
  JS:     at AppAuthComponent.doAuthorize (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:19:17)
  JS:     at AppAuthComponent_Template_Button_tap_2_listener (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:55:90)
  JS:     at executeListenerWithErrorHandling (file: src/webpack:/ns-ng-open-native/node_modules/@angular/core/fesm2015/core.mjs:13956:0)
  JS:     at Object.wrapListenerIn_markDirtyAndPreventDefault (file:///data/data/org.nativescript.nsngopennative/files/...

You probably already know, but converter.js:281 is here :)

export function promisify(module, methodName, methodTypes, args) {
    return new Promise((resolve, reject) => {
        try {
            module[methodName](...toNativeArguments(methodTypes, args, resolve, reject)); // line 281
        }
        catch (e) {
            reject(e);
        }
    });
}

Does it work when you pass all params like before or it's not working even then?

fpaaske commented 1 year ago

Hm, good point. I'll test this and report back.

ammarahm-ed commented 1 year ago

Ah i think I found what's wrong. will fix!

fpaaske commented 1 year ago

For what it's worth, I got the same error with all parameters filled out:

  JS: CONSOLE LOG: Error: ����
  JS:     at file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:281:0
  JS:     at new ZoneAwarePromise (file: src/webpack:/ns-ng-open-native/node_modules/zone.js/fesm2015/zone.js:1411:0)
  JS:     at promisify (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:279:0)
  JS:     at <computed> [as authorize] (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/nativemodules.js:87:36)
  JS:     at authorize (file: src/webpack:/ns-ng-open-native/node_modules/react-native-app-auth/index.js:246:19)
  JS:     at AppAuthComponent.doAuthorize (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:19:17)
  JS:     at AppAuthComponent_Template_Button_tap_2_listener (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:69:90)
  JS:     at executeListenerWithErrorHandling (file: src/webpack:/ns-ng-open-native/node_modules/@angular/core/fesm2015/core.mjs:13956:0)
  JS:     at Object.wrapListenerIn_markDirtyAndPreventDefault (file:///data/data/org.nativescript.nsngopennative/files/...

Then calling authorize again gives this error (no change in my code):

  JS: CONSOLE LOG: Error: No javascript exception or message provided.
  JS:     at file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:281:0
  JS:     at new ZoneAwarePromise (file: src/webpack:/ns-ng-open-native/node_modules/zone.js/fesm2015/zone.js:1411:0)
  JS:     at promisify (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/converter.js:279:0)
  JS:     at <computed> [as authorize] (file: src/webpack:/ns-ng-open-native/node_modules/@open-native/core/src/android/nativemodules.js:87:36)
  JS:     at authorize (file: src/webpack:/ns-ng-open-native/node_modules/react-native-app-auth/index.js:246:19)
  JS:     at AppAuthComponent.doAuthorize (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:19:17)
  JS:     at AppAuthComponent_Template_Button_tap_2_listener (file: src/webpack:/ns-ng-open-native/src/app/app-auth/app-auth.component.ts:69:90)
  JS:     at executeListenerWithErrorHandling (file: src/webpack:/ns-ng-open-native/node_modules/@angular/core/fesm2015/core.mjs:13956:0)
  JS:     at Object.wrapListenerIn_markDirtyAndPreventDefault (file:///data/data/org.nat...

Edit: This happens also when just providing the necessary params.

ammarahm-ed commented 1 year ago

Yeah, i just found out we are handling booleans and doubles wrong in Android when they are supposed to be a java object so it's causing error.

ammarahm-ed commented 1 year ago

Hey @fpaaske can you try the latest alpha? The module is now working on my end on android.

fpaaske commented 1 year ago

@ammarahm-ed I've tested again with the latest alpha.37, and now the authorize()-method starts the authorization process also for Android, with optional parameters being undefined(!), but the promise never seems to resolve or reject. There's no logging in either then() or catch() after authorize().

On iOS it still seems to work nicely.

(I've updated the issue repo as well)

ammarahm-ed commented 1 year ago

@ammarahm-ed I've tested again with the latest alpha.37, and now the authorize()-method starts the authorization process also for Android, with optional parameters being undefined(!), but the promise never seems to resolve or reject. There's no logging in either then() or catch() after authorize().

On iOS it still seems to work nicely.

(I've updated the issue repo as well)

Got it, seems like everything is working now except receiving the intent when app is reopened again. Therefore the promise never resolves. Will fix! Thanks for keeping up haha!

ammarahm-ed commented 1 year ago

Hey @fpaaske good news, it works now with latest alpha, I am able to login & logout successfully!

fpaaske commented 1 year ago

I can confirm that this is working now! Thanks for all the help @ammarahm-ed, you're a legend!