Swallowed message : Error: Uncaught (in promise): [object Undefined]
Asked Answered
R

3

14

My login component briefly displays before being removed by an error message about an undefined object in a promise.

Here is the promise definition:

  static init(): Promise<any> {
    KeycloakClientService.auth.loggedIn = false;
    return new Promise((resolve, reject) => {
      const keycloakConfig = {
      url: environment.KEYCLOAK_URL,
      realm: environment.KEYCLOAK_REALM,
      clientId: environment.KEYCLOAK_CLIENTID,
      'ssl-required': 'external',
      'public-client': true
      };
      const keycloakAuth: any = new Keycloak(keycloakConfig);

      keycloakAuth.init({onLoad: 'check-sso'})
        .success(() => {
          KeycloakClientService.auth.loggedIn = true;
          KeycloakClientService.auth.authz = keycloakAuth;
          KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
          + '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
          + document.baseURI;
          console.log('=======>> The keycloak client has been initiated successfully');
          resolve('Succeeded in initiating the keycloak client');
        })
        .error(() => {
          reject('Failed to initiate the keycloak client');
        });
    });
  }

It is called by:

KeycloakClientService.init()
  .then(
    () => {
      console.log('The keycloak client has been initialized');
    }
  )
  .catch(
    (error) => {
      console.log(error);
      window.location.reload();
    }
  );

The console shows both messages:

The keycloak client has been initiated successfully
The keycloak client has been initialized

I'm using Angular 6.0.4 and tried following this blog

Any way to work around this error so as to keep my login form displayed ?

UPDATE: I tried using an observable instead of a promise but the issue remained the same:

  public init(): Observable<any> {
    KeycloakClientService.auth.loggedIn = false;
    return new Observable((observer) => {
      const keycloakConfig = {
        'url': environment.KEYCLOAK_URL,
        'realm': environment.KEYCLOAK_REALM,
        'clientId': environment.KEYCLOAK_CLIENTID,
        'ssl-required': 'external',
        'public-client': true
      };
      const keycloakAuth: any = new Keycloak(keycloakConfig);

      keycloakAuth.init({ 'onLoad': 'check-sso' })
        .success(() => {
          KeycloakClientService.auth.loggedIn = true;
          KeycloakClientService.auth.authz = keycloakAuth;
          KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
            + '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
            + document.baseURI;
          console.log('The keycloak auth has been initialized');
          observer.next('Succeeded in initiating the keycloak client');
          observer.complete();
        })
        .error(() => {
          console.log('The keycloak client could not be initiated');
          observer.error('Failed to initiate the keycloak client');
        });
    });
  }

The whole source code is available on GitHub

UPDATE: Following an answer below, I also tried to use a then() and a catch() keywords but the error remained the exact same:

keycloakAuth.init({ 'onLoad': 'check-sso' })
        .then(() => {
          KeycloakClientService.auth.loggedIn = true;
          KeycloakClientService.auth.authz = keycloakAuth;
          KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
            + '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
            + document.baseURI;
          console.log('The keycloak auth has been initialized');
          observer.next('Succeeded in initiating the keycloak client');
          observer.complete();
        })
        .catch(() => {
          console.log('The keycloak client could not be initiated');
          observer.error('Failed to initiate the keycloak client');
        });
Rato answered 24/4, 2018 at 15:27 Comment(7)
The order of the console messages appears to be wrong. How can the keycloadClientService.init().then(...) be called before the resolve('Succeeded') is called? The message with the word "successfully" is logged before the resolve function is called. Are you sure this code example is correct?Bumboat
Also, does the (error) => console.log(error) catch this error?Bumboat
@ogTag My mistake, the loggers appear indeed in the other order. I now corrected it. And no, the catch is not firing.Rato
Would you mind sharing your error code ? This would help a lot.Merit
@trichetriche There is no such thing. The only message I got is Uncaught (in promise): [object Undefined]. I put the whole source code on GitHub if needed.Rato
And there probably is a stack trace, stating the file and line where it went wrong, isn't it ?Merit
@trichetriche No, not at all. The error is swallowed by the promise. Hence my question title...Rato
B
3

This is a wild guess, but maybe it's a conflict with Angular's zones. Since this is a security library it might not like that Angular has replaced core functions with proxies. For example, NgZone modifies window.setTimeout and the HTTP methods.

So you could try running this code outside of zones. The only problem here is that you're using a static function, and will have to make this an injectable service so that you can access NgZone

@Injectable()
export class KeycloakClientService {
    public constructor(private zone: NgZone) {
    }

    public init(): Promise<any> {
        KeycloakClientService.auth.loggedIn = false;
        return new Promise((resolve, reject) => {
            this.zone.runOutsideAngular(() => {
                const keycloakConfig = {
                     url: environment.KEYCLOAK_URL,
                     realm: environment.KEYCLOAK_REALM,
                     clientId: environment.KEYCLOAK_CLIENTID,
                     'ssl-required': 'external',
                     'public-client': true
                };

                const keycloakAuth: any = new Keycloak(keycloakConfig);

                keycloakAuth.init({onLoad: 'check-sso'})
                    .success(() => {
                        KeycloakClientService.auth.loggedIn = true;
                        KeycloakClientService.auth.authz = keycloakAuth;
                        KeycloakClientService.auth.logoutUrl = environment.KEYCLOAK_URL
                            + '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
                            + document.baseURI;
                        console.log('=======>> The keycloak client has been initiated successfully');
                        resolve('Succeeded in initiating the keycloak client');
                    })
                    .error(() => {
                        reject('Failed to initiate the keycloak client');
                    });
            });
        }
    }
}

The change here is to use zone.runOutsideAngular

Bumboat answered 7/6, 2018 at 13:50 Comment(9)
But then, how to inject the keycloakClientService into the main.ts file ?Rato
But then, do I really need to have the KeycloakClientService.init()call in the main.ts file ? Wouldn't it be better to have it in the AppModule or in the AppComponent class constructors ?Rato
@Rato yes, you should put this in the AppModule constructor. Don't put in the constructor of a module that is lazy loaded. If this is something that must be done and finished before the AppComponent is shown, then you should put this at the top of your routes as a Resolver.Bumboat
I put it in the AppModule constructor, and called the ìnit() method on an injected instance of the keycloakClientService service. I had the promise wrapped up in an runOutsideAngular block. But nothing changed, the same two loggers were called in the same order, alonside the same above errors.Rato
It's not possible for the success callback to be executed if that promise has an unhandled error. I think this library starts an internal promise that throws the error. It's not from your promise. You might have triggered this other promise. I don't know what to do. You need the help from people who are using this library. I don't think this is an Angular issue.Bumboat
I sorted out my error handler and now I can say the error is being caught. I put the whole application at https://github.com/stephaneeybert/ng-hero if that can be of interest.Rato
Thanks. I'll clone it and give it a try.Bumboat
I see it is when the success block of keycloakAuth.init({onLoad: 'check-sso'}) completes that the error occurs. Removing all code in the block, even removing the resolve() call, doesn't change anything. It is the exiting of this success block that lets the error occur.Rato
Do you think I should log an issue at https://issues.jboss.org/projects/KEYCLOAK/issues/ ?Rato
S
0

If you remove the success block, where do you run your logic within success?

I read some of their source code, I think this is why success causes the problem:

Within keycloak.js, there is a function createNativePromise():

function createNativePromise() {
var p = {
    setSuccess: function(result) {
        p.success = true;
        p.resolve(result);
    },

    setError: function(result) {
        p.success = false;
        p.reject(result);
    }
};
p.promise = new Promise(function(resolve, reject) {
    p.resolve = resolve;
    p.reject = reject;
});
p.promise.success = function(callback) {
    p.promise.then(callback);
    return p.promise;
}
p.promise.error = function(callback) {
    p.promise.catch(callback);
    return p.promise;
}
return p;
}

And it's used this way(simplified code):

function refreshToken() {
    var promise = createNativePromise();
    ...
    if (refreshTokenFailed) {
        promise.setError(true);
    }
    ...

    return promise.promise;
}

The problem is, promise.setError() calls promise.reject(result), so the promise is rejected, it's expecting a catch.

But within promise.success, there is a promise.promise.then(callback);, and nobody is catching this promise.

This is why you get the Uncaught (in promise): [object Undefined], and in my case, i always get Uncaught (in promise): true.

Solution:

Notice that promise.promise is a real Promise, so we can use then and catch instead of success and error.

The drawback is, the typescript type will be wrong.

Stated answered 31/7, 2018 at 16:23 Comment(2)
I tried to use a then() and a catch() keywords but the error remained the exact same. I added an update to the question for more details.Rato
@Rato sorry, you are right, I also still get the error.Stated
P
0

We have observed a similar error about the promise object undefined. The situation was our local application was working fine with the local keycloak standalone server but faced this error when the local application trying to connect with a keycloak server hosted on the ABC server (ABC is used as a reference here to give any arbitrary name).

This issue was resolved when we hosted the application and the keycloak server both on the ABC server.

It looks like there are some time sync issues in different machines due to which the promise object is not returned.

Pneumococcus answered 1/4, 2021 at 13:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.