Mon composant de connexion s'affiche brièvement avant d'être supprimé par un message d'erreur concernant un objet non défini dans une promesse.
Voici la définition de la promesse:
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');
});
});
}
Il s'appelle par:
KeycloakClientService.init()
.then(
() => {
console.log('The keycloak client has been initialized');
}
)
.catch(
(error) => {
console.log(error);
window.location.reload();
}
);
La console affiche les deux messages:
The keycloak client has been initiated successfully
The keycloak client has been initialized
J'utilise Angular 6.0.4
et j'ai essayé de suivre ceci blog
Est-il possible de contourner cette erreur afin de garder mon formulaire de connexion affiché?
UPDATE: J'ai essayé d'utiliser un observable au lieu d'une promesse mais le problème est resté le même:
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');
});
});
}
Le code source complet est disponible sur GitHub
MISE À JOUR: suite à la réponse ci-dessous, j'ai également essayé d'utiliser les mots-clés then()
et catch()
, mais l'erreur est restée identique:
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');
});
C'est une conjecture sauvage, mais c'est peut-être un conflit avec les zones d'Angular. Dans la mesure où il s’agit d’une bibliothèque de sécurité, il est possible que Angular ne remplace pas les fonctions de base par des serveurs proxy. Par exemple, NgZone
modifie window.setTimeout
et les méthodes HTTP.
Donc, vous pouvez essayer d'exécuter ce code en dehors des zones. Le seul problème ici est que vous utilisez une fonction static et que vous devrez en faire un service injectable afin que vous puissiez accéder à 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');
});
});
}
}
}
Le changement ici consiste à utiliser zone.runOutsideAngular
Si vous supprimez le bloc success
, où exécutez-vous votre logique dans success
?
J'ai lu une partie de leur code source, je pense que c'est pourquoi success
est à l'origine du problème:
Dans keycloak.js
, il existe une fonction 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;
}
Et il est utilisé de cette façon (code simplifié):
function refreshToken() {
var promise = createNativePromise();
...
if (refreshTokenFailed) {
promise.setError(true);
}
...
return promise.promise;
}
Le problème est que promise.setError()
appelle promise.reject(result)
, donc la promesse est rejetée, elle attend une catch
.
Mais dans promise.success
, il existe une promise.promise.then(callback);
, et personne ne prend cette promesse.
C'est pourquoi vous obtenez le Uncaught (in promise): [object Undefined]
, et dans mon cas, je reçois toujours le Uncaught (in promise): true
.
Solution:
Notez que promise.promise
est une vraie Promise
. Nous pouvons donc utiliser then
et catch
au lieu de success
et error
.
L'inconvénient est que le type TypeScript sera incorrect.