KEYCLOAK-9346 Add new KeycloakPromise to support native promises

Co-authored-by: mhajas <mhajas@redhat.com>
This commit is contained in:
Jon Koops 2019-12-23 19:52:30 +01:00 committed by Stian Thorgersen
parent bcb542d9cc
commit c1bf183998
5 changed files with 161 additions and 258 deletions

View file

@ -26,7 +26,7 @@ export = Keycloak;
* Creates a new Keycloak client instance. * Creates a new Keycloak client instance.
* @param config A configuration object or path to a JSON config file. * @param config A configuration object or path to a JSON config file.
*/ */
declare function Keycloak<TPromise extends Keycloak.KeycloakPromiseType = 'legacy'>(config?: Keycloak.KeycloakConfig | string): Keycloak.KeycloakInstance<TPromise>; declare function Keycloak(config?: Keycloak.KeycloakConfig | string): Keycloak.KeycloakInstance;
declare namespace Keycloak { declare namespace Keycloak {
type KeycloakAdapterName = 'cordova' | 'cordova-native' |'default' | any; type KeycloakAdapterName = 'cordova' | 'cordova-native' |'default' | any;
@ -34,7 +34,6 @@ declare namespace Keycloak {
type KeycloakResponseMode = 'query'|'fragment'; type KeycloakResponseMode = 'query'|'fragment';
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token'; type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
type KeycloakFlow = 'standard'|'implicit'|'hybrid'; type KeycloakFlow = 'standard'|'implicit'|'hybrid';
type KeycloakPromiseType = 'legacy' | 'native';
type KeycloakPkceMethod = 'S256'; type KeycloakPkceMethod = 'S256';
interface KeycloakConfig { interface KeycloakConfig {
@ -136,21 +135,6 @@ declare namespace Keycloak {
*/ */
flow?: KeycloakFlow; flow?: KeycloakFlow;
/**
* Set the promise type. If set to `native` all methods returning a promise
* will return a native JavaScript promise. If not not specified then
* Keycloak specific legacy promise objects will be returned instead.
*
* Since native promises have become the industry standard it is highly
* recommended that you always specify `native` as the promise type.
*
* Note that in upcoming versions of Keycloak the default will be changed
* to `native`, and support for legacy promises will eventually be removed.
*
* @default legacy
*/
promiseType?: KeycloakPromiseType;
/** /**
* Configures the Proof Key for Code Exchange (PKCE) method to use. * Configures the Proof Key for Code Exchange (PKCE) method to use.
* The currently allowed method is 'S256'. * The currently allowed method is 'S256'.
@ -226,14 +210,18 @@ declare namespace Keycloak {
type KeycloakPromiseCallback<T> = (result: T) => void; type KeycloakPromiseCallback<T> = (result: T) => void;
interface KeycloakPromise<TSuccess, TError> { class KeycloakPromise<TSuccess, TError> extends Promise<TSuccess> {
/** /**
* Function to call if the promised action succeeds. * Function to call if the promised action succeeds.
*
* @deprecated Use `.then()` instead.
*/ */
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>; success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
/** /**
* Function to call if the promised action throws an error. * Function to call if the promised action throws an error.
*
* @deprecated Use `.catch()` instead.
*/ */
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>; error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
} }
@ -281,20 +269,11 @@ declare namespace Keycloak {
roles: string[]; roles: string[];
} }
// export interface KeycloakUserInfo {}
/**
* Conditional CompatPromise type in order to support
* both legacy promises and native promises as return types.
*/
type CompatPromise<TPromiseType extends KeycloakPromiseType, TSuccess, TError> =
TPromiseType extends 'native' ? Promise<TSuccess> : KeycloakPromise<TSuccess, TError>;
/** /**
* A client for the Keycloak authentication server. * A client for the Keycloak authentication server.
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation} * @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
*/ */
interface KeycloakInstance<TPromise extends KeycloakPromiseType = 'legacy'> { interface KeycloakInstance {
/** /**
* Is true if the user is authenticated, false otherwise. * Is true if the user is authenticated, false otherwise.
*/ */
@ -459,32 +438,32 @@ declare namespace Keycloak {
* @param initOptions Initialization options. * @param initOptions Initialization options.
* @returns A promise to set functions to be invoked on success or error. * @returns A promise to set functions to be invoked on success or error.
*/ */
init(initOptions: KeycloakInitOptions): CompatPromise<TPromise, boolean, KeycloakError>; init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
/** /**
* Redirects to login form. * Redirects to login form.
* @param options Login options. * @param options Login options.
*/ */
login(options?: KeycloakLoginOptions): CompatPromise<TPromise, void, void>; login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
/** /**
* Redirects to logout. * Redirects to logout.
* @param options Logout options. * @param options Logout options.
* @param options.redirectUri Specifies the uri to redirect to after logout. * @param options.redirectUri Specifies the uri to redirect to after logout.
*/ */
logout(options?: any): CompatPromise<TPromise, void, void>; logout(options?: any): KeycloakPromise<void, void>;
/** /**
* Redirects to registration form. * Redirects to registration form.
* @param options Supports same options as Keycloak#login but `action` is * @param options Supports same options as Keycloak#login but `action` is
* set to `'register'`. * set to `'register'`.
*/ */
register(options?: any): CompatPromise<TPromise, void, void>; register(options?: any): KeycloakPromise<void, void>;
/** /**
* Redirects to the Account Management Console. * Redirects to the Account Management Console.
*/ */
accountManagement(): CompatPromise<TPromise, void, void>; accountManagement(): KeycloakPromise<void, void>;
/** /**
* Returns the URL to login form. * Returns the URL to login form.
@ -536,7 +515,7 @@ declare namespace Keycloak {
* alert('Failed to refresh the token, or the session has expired'); * alert('Failed to refresh the token, or the session has expired');
* }); * });
*/ */
updateToken(minValidity: number): CompatPromise<TPromise, boolean, boolean>; updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
/** /**
* Clears authentication state, including tokens. This can be useful if * Clears authentication state, including tokens. This can be useful if
@ -563,11 +542,11 @@ declare namespace Keycloak {
* Loads the user's profile. * Loads the user's profile.
* @returns A promise to set functions to be invoked on success or error. * @returns A promise to set functions to be invoked on success or error.
*/ */
loadUserProfile(): CompatPromise<TPromise, KeycloakProfile, void>; loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
/** /**
* @private Undocumented. * @private Undocumented.
*/ */
loadUserInfo(): CompatPromise<TPromise, {}, void>; loadUserInfo(): KeycloakPromise<{}, void>;
} }
} }

View file

@ -43,6 +43,16 @@
*/ */
(function (r) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = r() } else if (typeof define === "function" && define.amd) { define([], r) } else { var e; if (typeof window !== "undefined") { e = window } else if (typeof global !== "undefined") { e = global } else if (typeof self !== "undefined") { e = self } else { e = this } e.base64js = r() } })(function () { var r, e, n; return function () { function r(e, n, t) { function o(f, i) { if (!n[f]) { if (!e[f]) { var u = "function" == typeof require && require; if (!i && u) return u(f, !0); if (a) return a(f, !0); var v = new Error("Cannot find module '" + f + "'"); throw v.code = "MODULE_NOT_FOUND", v } var d = n[f] = { exports: {} }; e[f][0].call(d.exports, function (r) { var n = e[f][1][r]; return o(n || r) }, d, d.exports, r, e, n, t) } return n[f].exports } for (var a = "function" == typeof require && require, f = 0; f < t.length; f++)o(t[f]); return o } return r }()({ "/": [function (r, e, n) { "use strict"; n.byteLength = d; n.toByteArray = h; n.fromByteArray = p; var t = []; var o = []; var a = typeof Uint8Array !== "undefined" ? Uint8Array : Array; var f = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (var i = 0, u = f.length; i < u; ++i) { t[i] = f[i]; o[f.charCodeAt(i)] = i } o["-".charCodeAt(0)] = 62; o["_".charCodeAt(0)] = 63; function v(r) { var e = r.length; if (e % 4 > 0) { throw new Error("Invalid string. Length must be a multiple of 4") } var n = r.indexOf("="); if (n === -1) n = e; var t = n === e ? 0 : 4 - n % 4; return [n, t] } function d(r) { var e = v(r); var n = e[0]; var t = e[1]; return (n + t) * 3 / 4 - t } function c(r, e, n) { return (e + n) * 3 / 4 - n } function h(r) { var e; var n = v(r); var t = n[0]; var f = n[1]; var i = new a(c(r, t, f)); var u = 0; var d = f > 0 ? t - 4 : t; for (var h = 0; h < d; h += 4) { e = o[r.charCodeAt(h)] << 18 | o[r.charCodeAt(h + 1)] << 12 | o[r.charCodeAt(h + 2)] << 6 | o[r.charCodeAt(h + 3)]; i[u++] = e >> 16 & 255; i[u++] = e >> 8 & 255; i[u++] = e & 255 } if (f === 2) { e = o[r.charCodeAt(h)] << 2 | o[r.charCodeAt(h + 1)] >> 4; i[u++] = e & 255 } if (f === 1) { e = o[r.charCodeAt(h)] << 10 | o[r.charCodeAt(h + 1)] << 4 | o[r.charCodeAt(h + 2)] >> 2; i[u++] = e >> 8 & 255; i[u++] = e & 255 } return i } function s(r) { return t[r >> 18 & 63] + t[r >> 12 & 63] + t[r >> 6 & 63] + t[r & 63] } function l(r, e, n) { var t; var o = []; for (var a = e; a < n; a += 3) { t = (r[a] << 16 & 16711680) + (r[a + 1] << 8 & 65280) + (r[a + 2] & 255); o.push(s(t)) } return o.join("") } function p(r) { var e; var n = r.length; var o = n % 3; var a = []; var f = 16383; for (var i = 0, u = n - o; i < u; i += f) { a.push(l(r, i, i + f > u ? u : i + f)) } if (o === 1) { e = r[n - 1]; a.push(t[e >> 2] + t[e << 4 & 63] + "==") } else if (o === 2) { e = (r[n - 2] << 8) + r[n - 1]; a.push(t[e >> 10] + t[e >> 4 & 63] + t[e << 2 & 63] + "=") } return a.join("") } }, {}] }, {}, [])("/") }); (function (r) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = r() } else if (typeof define === "function" && define.amd) { define([], r) } else { var e; if (typeof window !== "undefined") { e = window } else if (typeof global !== "undefined") { e = global } else if (typeof self !== "undefined") { e = self } else { e = this } e.base64js = r() } })(function () { var r, e, n; return function () { function r(e, n, t) { function o(f, i) { if (!n[f]) { if (!e[f]) { var u = "function" == typeof require && require; if (!i && u) return u(f, !0); if (a) return a(f, !0); var v = new Error("Cannot find module '" + f + "'"); throw v.code = "MODULE_NOT_FOUND", v } var d = n[f] = { exports: {} }; e[f][0].call(d.exports, function (r) { var n = e[f][1][r]; return o(n || r) }, d, d.exports, r, e, n, t) } return n[f].exports } for (var a = "function" == typeof require && require, f = 0; f < t.length; f++)o(t[f]); return o } return r }()({ "/": [function (r, e, n) { "use strict"; n.byteLength = d; n.toByteArray = h; n.fromByteArray = p; var t = []; var o = []; var a = typeof Uint8Array !== "undefined" ? Uint8Array : Array; var f = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (var i = 0, u = f.length; i < u; ++i) { t[i] = f[i]; o[f.charCodeAt(i)] = i } o["-".charCodeAt(0)] = 62; o["_".charCodeAt(0)] = 63; function v(r) { var e = r.length; if (e % 4 > 0) { throw new Error("Invalid string. Length must be a multiple of 4") } var n = r.indexOf("="); if (n === -1) n = e; var t = n === e ? 0 : 4 - n % 4; return [n, t] } function d(r) { var e = v(r); var n = e[0]; var t = e[1]; return (n + t) * 3 / 4 - t } function c(r, e, n) { return (e + n) * 3 / 4 - n } function h(r) { var e; var n = v(r); var t = n[0]; var f = n[1]; var i = new a(c(r, t, f)); var u = 0; var d = f > 0 ? t - 4 : t; for (var h = 0; h < d; h += 4) { e = o[r.charCodeAt(h)] << 18 | o[r.charCodeAt(h + 1)] << 12 | o[r.charCodeAt(h + 2)] << 6 | o[r.charCodeAt(h + 3)]; i[u++] = e >> 16 & 255; i[u++] = e >> 8 & 255; i[u++] = e & 255 } if (f === 2) { e = o[r.charCodeAt(h)] << 2 | o[r.charCodeAt(h + 1)] >> 4; i[u++] = e & 255 } if (f === 1) { e = o[r.charCodeAt(h)] << 10 | o[r.charCodeAt(h + 1)] << 4 | o[r.charCodeAt(h + 2)] >> 2; i[u++] = e >> 8 & 255; i[u++] = e & 255 } return i } function s(r) { return t[r >> 18 & 63] + t[r >> 12 & 63] + t[r >> 6 & 63] + t[r & 63] } function l(r, e, n) { var t; var o = []; for (var a = e; a < n; a += 3) { t = (r[a] << 16 & 16711680) + (r[a + 1] << 8 & 65280) + (r[a + 2] & 255); o.push(s(t)) } return o.join("") } function p(r) { var e; var n = r.length; var o = n % 3; var a = []; var f = 16383; for (var i = 0, u = n - o; i < u; i += f) { a.push(l(r, i, i + f > u ? u : i + f)) } if (o === 1) { e = r[n - 1]; a.push(t[e >> 2] + t[e << 4 & 63] + "==") } else if (o === 2) { e = (r[n - 2] << 8) + r[n - 1]; a.push(t[e >> 10] + t[e >> 4 & 63] + t[e << 2 & 63] + "=") } return a.join("") } }, {}] }, {}, [])("/") });
/**
* [promise-polyfill]{@link https://github.com/taylorhakes/promise-polyfill}
*
* @version v8.1.3
* @author Hakes, Taylor
* @copyright Hakes, Taylor 2014
* @license MIT
*/
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(e){return!(!e||"undefined"==typeof e.length)}function t(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],c(e,this)}function r(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(r){return void f(n.promise,r)}i(n.promise,o)}else(1===e._state?i:f)(n.promise,e._value)})):e._deferreds.push(n)}function i(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var t=n.then;if(n instanceof o)return e._state=3,e._value=n,void u(e);if("function"==typeof t)return void c(function(e,n){return function(){e.apply(n,arguments)}}(t,n),e)}e._state=1,e._value=n,u(e)}catch(r){f(e,r)}}function f(e,n){e._state=2,e._value=n,u(e)}function u(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;t>n;n++)r(e,e._deferreds[n]);e._deferreds=null}function c(e,n){var t=!1;try{e(function(e){t||(t=!0,i(n,e))},function(e){t||(t=!0,f(n,e))})}catch(o){if(t)return;t=!0,f(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,n){var o=new this.constructor(t);return r(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,n,o)),o},o.prototype["finally"]=e,o.all=function(e){return new o(function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,function(n){r(e,n)},o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(t,r){if(!n(e))return r(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)o.resolve(e[i]).then(t,r)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=o});
var Keycloak = factory( root["sha256"], root["base64js"] ); var Keycloak = factory( root["sha256"], root["base64js"] );
root["Keycloak"] = Keycloak; root["Keycloak"] = Keycloak;
@ -51,6 +61,51 @@
} }
} }
})(window, function (sha256_imported, base64js_imported) { })(window, function (sha256_imported, base64js_imported) {
if (typeof Promise === 'undefined') {
throw Error('Keycloak requires an environment that supports Promises. Make sure that you include the appropriate polyfill.');
}
var loggedPromiseDeprecation = false;
function logPromiseDeprecation() {
if (!loggedPromiseDeprecation) {
loggedPromiseDeprecation = true;
console.warn('[KEYCLOAK] Usage of legacy style promise methods such as `.error()` and `.success()` has been deprecated and support will be removed in future versions. Use standard style promise methods such as `.then() and `.catch()` instead.');
}
}
function toKeycloakPromise(promise) {
promise.__proto__ = KeycloakPromise.prototype;
return promise;
}
function KeycloakPromise(executor) {
return toKeycloakPromise(new Promise(executor));
}
KeycloakPromise.prototype = Object.create(Promise.prototype);
KeycloakPromise.prototype.constructor = KeycloakPromise;
KeycloakPromise.prototype.success = function(callback) {
logPromiseDeprecation();
var promise = this.then(function handleSuccess(value) {
callback(value);
});
return toKeycloakPromise(promise);
};
KeycloakPromise.prototype.error = function(callback) {
logPromiseDeprecation();
var promise = this.catch(function handleError(error) {
callback(error);
});
return toKeycloakPromise(promise);
};
function Keycloak (config) { function Keycloak (config) {
if (!(this instanceof Keycloak)) { if (!(this instanceof Keycloak)) {
return new Keycloak(config); return new Keycloak(config);
@ -109,13 +164,6 @@
loginIframe.interval = initOptions.checkLoginIframeInterval; loginIframe.interval = initOptions.checkLoginIframeInterval;
} }
if (initOptions.promiseType === 'native') {
kc.useNativePromise = true;
} else {
console.warn('[KEYCLOAK] Using legacy promises is deprecated and will be removed in future versions. You can opt in to using native promises by setting `promiseType` to \'native\' when initializing Keycloak.');
kc.useNativePromise = false;
}
if (initOptions.onLoad === 'login-required') { if (initOptions.onLoad === 'login-required') {
kc.loginRequired = true; kc.loginRequired = true;
} }
@ -179,13 +227,13 @@
kc.flow = 'standard'; kc.flow = 'standard';
} }
var promise = createPromise(false); var promise = createPromise();
var initPromise = createPromise(true); var initPromise = createPromise();
initPromise.promise.success(function() { initPromise.promise.then(function() {
kc.onReady && kc.onReady(kc.authenticated); kc.onReady && kc.onReady(kc.authenticated);
promise.setSuccess(kc.authenticated); promise.setSuccess(kc.authenticated);
}).error(function(errorData) { }).catch(function(errorData) {
promise.setError(errorData); promise.setError(errorData);
}); });
@ -196,19 +244,12 @@
if (!prompt) { if (!prompt) {
options.prompt = 'none'; options.prompt = 'none';
} }
if (kc.useNativePromise) {
kc.login(options).then(function () { kc.login(options).then(function () {
initPromise.setSuccess(); initPromise.setSuccess();
}).catch(function () { }).catch(function () {
initPromise.setError(); initPromise.setError();
}); });
} else {
kc.login(options).success(function () {
initPromise.setSuccess();
}).error(function () {
initPromise.setError();
});
}
} }
var checkSsoSilently = function() { var checkSsoSilently = function() {
@ -238,14 +279,14 @@
switch (initOptions.onLoad) { switch (initOptions.onLoad) {
case 'check-sso': case 'check-sso':
if (loginIframe.enable) { if (loginIframe.enable) {
setupCheckLoginIframe().success(function() { setupCheckLoginIframe().then(function() {
checkLoginIframe().success(function (unchanged) { checkLoginIframe().then(function (unchanged) {
if (!unchanged) { if (!unchanged) {
kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false); kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
} else { } else {
initPromise.setSuccess(); initPromise.setSuccess();
} }
}).error(function () { }).catch(function () {
initPromise.setError(); initPromise.setError();
}); });
}); });
@ -269,9 +310,9 @@
} }
if (callback && callback.valid) { if (callback && callback.valid) {
return setupCheckLoginIframe().success(function() { return setupCheckLoginIframe().then(function() {
processCallback(callback, initPromise); processCallback(callback, initPromise);
}).error(function (e) { }).catch(function (e) {
initPromise.setError(); initPromise.setError();
}); });
} else if (initOptions) { } else if (initOptions) {
@ -279,8 +320,8 @@
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
if (loginIframe.enable) { if (loginIframe.enable) {
setupCheckLoginIframe().success(function() { setupCheckLoginIframe().then(function() {
checkLoginIframe().success(function (unchanged) { checkLoginIframe().then(function (unchanged) {
if (unchanged) { if (unchanged) {
kc.onAuthSuccess && kc.onAuthSuccess(); kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess(); initPromise.setSuccess();
@ -288,15 +329,15 @@
} else { } else {
initPromise.setSuccess(); initPromise.setSuccess();
} }
}).error(function () { }).catch(function () {
initPromise.setError(); initPromise.setError();
}); });
}); });
} else { } else {
kc.updateToken(-1).success(function() { kc.updateToken(-1).then(function() {
kc.onAuthSuccess && kc.onAuthSuccess(); kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess(); initPromise.setSuccess();
}).error(function() { }).catch(function() {
kc.onAuthError && kc.onAuthError(); kc.onAuthError && kc.onAuthError();
if (initOptions.onLoad) { if (initOptions.onLoad) {
onLoad(); onLoad();
@ -315,8 +356,8 @@
} }
} }
configPromise.success(processInit); configPromise.then(processInit);
configPromise.error(function() { configPromise.catch(function() {
promise.setError(); promise.setError();
}); });
@ -512,7 +553,7 @@
req.setRequestHeader('Accept', 'application/json'); req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Authorization', 'bearer ' + kc.token); req.setRequestHeader('Authorization', 'bearer ' + kc.token);
var promise = createPromise(false); var promise = createPromise();
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
@ -537,7 +578,7 @@
req.setRequestHeader('Accept', 'application/json'); req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Authorization', 'bearer ' + kc.token); req.setRequestHeader('Authorization', 'bearer ' + kc.token);
var promise = createPromise(false); var promise = createPromise();
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
@ -576,7 +617,7 @@
} }
kc.updateToken = function(minValidity) { kc.updateToken = function(minValidity) {
var promise = createPromise(false); var promise = createPromise();
if (!kc.refreshToken) { if (!kc.refreshToken) {
promise.setError(); promise.setError();
@ -650,9 +691,9 @@
if (loginIframe.enable) { if (loginIframe.enable) {
var iframePromise = checkLoginIframe(); var iframePromise = checkLoginIframe();
iframePromise.success(function() { iframePromise.then(function() {
exec(); exec();
}).error(function() { }).catch(function() {
promise.setError(); promise.setError();
}); });
} else { } else {
@ -769,7 +810,7 @@
} }
function loadConfig(url) { function loadConfig(url) {
var promise = createPromise(true); var promise = createPromise();
var configUrl; var configUrl;
if (!config) { if (!config) {
@ -1117,15 +1158,7 @@
return result; return result;
} }
function createPromise(internal) { function createPromise() {
if (!internal && kc.useNativePromise) {
return createNativePromise();
} else {
return createLegacyPromise();
}
}
function createNativePromise() {
// Need to create a native Promise which also preserves the // Need to create a native Promise which also preserves the
// interface of the custom promise type previously used by the API // interface of the custom promise type previously used by the API
var p = { var p = {
@ -1137,55 +1170,16 @@
p.reject(result); p.reject(result);
} }
}; };
p.promise = new Promise(function(resolve, reject) { p.promise = new KeycloakPromise(function(resolve, reject) {
p.resolve = resolve; p.resolve = resolve;
p.reject = reject; p.reject = reject;
}); });
return p; return p;
} }
function createLegacyPromise() {
var p = {
setSuccess: function(result) {
p.success = true;
p.result = result;
if (p.successCallback) {
p.successCallback(result);
}
},
setError: function(result) {
p.error = true;
p.result = result;
if (p.errorCallback) {
p.errorCallback(result);
}
},
promise: {
success: function(callback) {
if (p.success) {
callback(p.result);
} else if (!p.error) {
p.successCallback = callback;
}
return p.promise;
},
error: function(callback) {
if (p.error) {
callback(p.result);
} else if (!p.success) {
p.errorCallback = callback;
}
return p.promise;
}
}
}
return p;
}
function setupCheckLoginIframe() { function setupCheckLoginIframe() {
var promise = createPromise(true); var promise = createPromise();
if (!loginIframe.enable) { if (!loginIframe.enable) {
promise.setSuccess(); promise.setSuccess();
@ -1251,7 +1245,7 @@
if (loginIframe.enable) { if (loginIframe.enable) {
if (kc.token) { if (kc.token) {
setTimeout(function() { setTimeout(function() {
checkLoginIframe().success(function(unchanged) { checkLoginIframe().then(function(unchanged) {
if (unchanged) { if (unchanged) {
scheduleCheckIframe(); scheduleCheckIframe();
} }
@ -1262,7 +1256,7 @@
} }
function checkLoginIframe() { function checkLoginIframe() {
var promise = createPromise(true); var promise = createPromise();
if (loginIframe.iframe && loginIframe.iframeOrigin ) { if (loginIframe.iframe && loginIframe.iframeOrigin ) {
var msg = kc.clientId + ' ' + (kc.sessionId ? kc.sessionId : ''); var msg = kc.clientId + ' ' + (kc.sessionId ? kc.sessionId : '');
@ -1283,17 +1277,17 @@
return { return {
login: function(options) { login: function(options) {
window.location.replace(kc.createLoginUrl(options)); window.location.replace(kc.createLoginUrl(options));
return createPromise(false).promise; return createPromise().promise;
}, },
logout: function(options) { logout: function(options) {
window.location.replace(kc.createLogoutUrl(options)); window.location.replace(kc.createLogoutUrl(options));
return createPromise(false).promise; return createPromise().promise;
}, },
register: function(options) { register: function(options) {
window.location.replace(kc.createRegisterUrl(options)); window.location.replace(kc.createRegisterUrl(options));
return createPromise(false).promise; return createPromise().promise;
}, },
accountManagement : function() { accountManagement : function() {
@ -1303,7 +1297,7 @@
} else { } else {
throw "Not supported by the OIDC server"; throw "Not supported by the OIDC server";
} }
return createPromise(false).promise; return createPromise().promise;
}, },
redirectUri: function(options, encodeHash) { redirectUri: function(options, encodeHash) {
@ -1362,7 +1356,7 @@
return { return {
login: function(options) { login: function(options) {
var promise = createPromise(false); var promise = createPromise();
var cordovaOptions = createCordovaOptions(options); var cordovaOptions = createCordovaOptions(options);
var loginUrl = kc.createLoginUrl(options); var loginUrl = kc.createLoginUrl(options);
@ -1410,7 +1404,7 @@
}, },
logout: function(options) { logout: function(options) {
var promise = createPromise(false); var promise = createPromise();
var logoutUrl = kc.createLogoutUrl(options); var logoutUrl = kc.createLogoutUrl(options);
var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes'); var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes');
@ -1445,7 +1439,7 @@
}, },
register : function(options) { register : function(options) {
var promise = createPromise(false); var promise = createPromise();
var registerUrl = kc.createRegisterUrl(); var registerUrl = kc.createRegisterUrl();
var cordovaOptions = createCordovaOptions(options); var cordovaOptions = createCordovaOptions(options);
var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions); var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions);
@ -1484,7 +1478,7 @@
return { return {
login: function(options) { login: function(options) {
var promise = createPromise(false); var promise = createPromise();
var loginUrl = kc.createLoginUrl(options); var loginUrl = kc.createLoginUrl(options);
universalLinks.subscribe('keycloak', function(event) { universalLinks.subscribe('keycloak', function(event) {
@ -1499,7 +1493,7 @@
}, },
logout: function(options) { logout: function(options) {
var promise = createPromise(false); var promise = createPromise();
var logoutUrl = kc.createLogoutUrl(options); var logoutUrl = kc.createLogoutUrl(options);
universalLinks.subscribe('keycloak', function(event) { universalLinks.subscribe('keycloak', function(event) {
@ -1514,7 +1508,7 @@
}, },
register : function(options) { register : function(options) {
var promise = createPromise(false); var promise = createPromise();
var registerUrl = kc.createRegisterUrl(options); var registerUrl = kc.createRegisterUrl(options);
universalLinks.subscribe('keycloak' , function(event) { universalLinks.subscribe('keycloak' , function(event) {
universalLinks.unsubscribe('keycloak'); universalLinks.unsubscribe('keycloak');

View file

@ -131,25 +131,12 @@ public class JavascriptTestExecutor {
String arguments = argumentsBuilder.build(); String arguments = argumentsBuilder.build();
String script; String script = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.init(" + arguments + ").then(function (authenticated) {" +
// phantomjs do not support Native promises " callback(\"Init Success (\" + (authenticated ? \"Authenticated\" : \"Not Authenticated\") + \")\");" +
if (argumentsBuilder.contains("promiseType", "native") && !"phantomjs".equals(System.getProperty("js.browser"))) { " }).catch(function () {" +
script = "var callback = arguments[arguments.length - 1];" + " callback(\"Init Error\");" +
" window.keycloak.init(" + arguments + ").then(function (authenticated) {" + " });";
" callback(\"Init Success (\" + (authenticated ? \"Authenticated\" : \"Not Authenticated\") + \")\");" +
" }, function () {" +
" callback(\"Init Error\");" +
" });";
} else {
script = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.init(" + arguments + ").success(function (authenticated) {" +
" callback(\"Init Success (\" + (authenticated ? \"Authenticated\" : \"Not Authenticated\") + \")\");" +
" }).error(function () {" +
" callback(\"Init Error\");" +
" });";
}
Object output = jsExecutor.executeAsyncScript(script); Object output = jsExecutor.executeAsyncScript(script);
@ -174,30 +161,16 @@ public class JavascriptTestExecutor {
} }
public JavascriptTestExecutor refreshToken(int value, JavascriptStateValidator validator) { public JavascriptTestExecutor refreshToken(int value, JavascriptStateValidator validator) {
String script; String script = "var callback = arguments[arguments.length - 1];" +
if (useNativePromises()) { " window.keycloak.updateToken(" + Integer.toString(value) + ").then(function (refreshed) {" +
script = "var callback = arguments[arguments.length - 1];" + " if (refreshed) {" +
" window.keycloak.updateToken(" + Integer.toString(value) + ").then(function (refreshed) {" + " callback(window.keycloak.tokenParsed);" +
" if (refreshed) {" + " } else {" +
" callback(window.keycloak.tokenParsed);" + " callback('Token not refreshed, valid for ' + Math.round(window.keycloak.tokenParsed.exp + window.keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');" +
" } else {" + " }" +
" callback('Token not refreshed, valid for ' + Math.round(window.keycloak.tokenParsed.exp + window.keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');" + " }).catch(function () {" +
" }" + " callback('Failed to refresh token');" +
" }, function () {" + " });";
" callback('Failed to refresh token');" +
" });";
} else {
script = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.updateToken(" + Integer.toString(value) + ").success(function (refreshed) {" +
" if (refreshed) {" +
" callback(window.keycloak.tokenParsed);" +
" } else {" +
" callback('Token not refreshed, valid for ' + Math.round(window.keycloak.tokenParsed.exp + window.keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');" +
" }" +
" }).error(function () {" +
" callback('Failed to refresh token');" +
" });";
}
Object output = jsExecutor.executeAsyncScript(script); Object output = jsExecutor.executeAsyncScript(script);
@ -208,12 +181,6 @@ public class JavascriptTestExecutor {
return this; return this;
} }
public boolean useNativePromises() {
return (boolean) jsExecutor.executeScript("if (typeof window.keycloak !== 'undefined') {" +
"return window.keycloak.useNativePromise" +
"} else { return false}");
}
public JavascriptTestExecutor openAccountPage(JavascriptStateValidator validator) { public JavascriptTestExecutor openAccountPage(JavascriptStateValidator validator) {
jsExecutor.executeScript("window.keycloak.accountManagement()"); jsExecutor.executeScript("window.keycloak.accountManagement()");
waitForPageToLoad(); waitForPageToLoad();
@ -234,22 +201,12 @@ public class JavascriptTestExecutor {
public JavascriptTestExecutor getProfile(JavascriptStateValidator validator) { public JavascriptTestExecutor getProfile(JavascriptStateValidator validator) {
String script; String script = "var callback = arguments[arguments.length - 1];" +
if (useNativePromises()) {
script = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.loadUserProfile().then(function (profile) {" + " window.keycloak.loadUserProfile().then(function (profile) {" +
" callback(profile);" + " callback(profile);" +
" }, function () {" + " }, function () {" +
" callback('Failed to load profile');" + " callback('Failed to load profile');" +
" });"; " });";
} else {
script = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.loadUserProfile().success(function (profile) {" +
" callback(profile);" +
" }).error(function () {" +
" callback('Failed to load profile');" +
" });";
}
Object output = jsExecutor.executeAsyncScript(script); Object output = jsExecutor.executeAsyncScript(script);

View file

@ -601,22 +601,22 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
@Test @Test
public void reentrancyCallbackTest() { public void reentrancyCallbackTest() {
testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn) testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
.executeAsyncScript( .executeAsyncScript(
"var callback = arguments[arguments.length - 1];" + "var callback = arguments[arguments.length - 1];" +
"keycloak.updateToken(60).success(function () {" + "keycloak.updateToken(60).then(function () {" +
" event(\"First callback\");" + " event(\"First callback\");" +
" keycloak.updateToken(60).success(function () {" + " keycloak.updateToken(60).then(function () {" +
" event(\"Second callback\");" + " event(\"Second callback\");" +
" callback(\"Success\");" + " callback(\"Success\");" +
" });" + " });" +
" }" + " }" +
");" ");"
, (driver1, output, events) -> { , (driver1, output, events) -> {
waitUntilElement(events).text().contains("First callback"); waitUntilElement(events).text().contains("First callback");
waitUntilElement(events).text().contains("Second callback"); waitUntilElement(events).text().contains("Second callback");
waitUntilElement(events).text().not().contains("Auth Logout"); waitUntilElement(events).text().not().contains("Auth Logout");
} }
); );
} }
@Test @Test
@ -644,5 +644,20 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
}); });
} }
@Test
public void testRefreshTokenWithDeprecatedPromiseHandles() {
String refreshWithDeprecatedHandles = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.updateToken(9999).success(function (refreshed) {" +
" callback('Success handle');" +
" }).catch(function () {" +
" callback('Catch handle');" +
" });";
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.executeAsyncScript(refreshWithDeprecatedHandles, assertOutputContains("Catch handle"))
.login(this::assertOnLoginPage)
.loginForm(testUser, this::assertOnTestAppUrl)
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
.executeAsyncScript(refreshWithDeprecatedHandles, assertOutputContains("Success handle"));
}
} }

View file

@ -1,42 +0,0 @@
package org.keycloak.testsuite.javascript;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.testsuite.util.javascript.JSObjectBuilder;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
public class JavascriptAdapterWithNativePromisesTest extends JavascriptAdapterTest {
@Override
protected JSObjectBuilder defaultArguments() {
return super.defaultArguments().add("promiseType", "native");
}
@Before
public void skipOnPhantomJS() {
Assume.assumeTrue("Native promises are not supported on PhantomJS", !"phantomjs".equals(System.getProperty("js.browser")));
}
@Test
@Override
public void reentrancyCallbackTest() {
testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
.executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"keycloak.updateToken(60).then(function () {" +
" event(\"First callback\");" +
" keycloak.updateToken(60).then(function () {" +
" event(\"Second callback\");" +
" callback(\"Success\");" +
" });" +
" }" +
");"
, (driver1, output, events) -> {
waitUntilElement(events).text().contains("First callback");
waitUntilElement(events).text().contains("Second callback");
waitUntilElement(events).text().not().contains("Auth Logout");
}
);
}
}