window.customElements.define('d4s-boot-2', class extends HTMLElement { #keycloak = null #authorization = null #url = null #realm = "d4science" #clientId = null #redirectUrl = null #audience = null // loading attempts nr and timer between attempts //#attempts = 6 //#timer = 500 #locked = true #queue = [] #interval = null constructor() { super() } unlock(){ this.#locked = false } connectedCallback(){ this.startStateChecker() this.loadKeycloak().then(()=>{ console.log("Keycloak loaded") return this.initKeycloak() }).then((authenticated)=>{ if(!authenticated) throw "Failed to authenticate"; console.log("Keycloak initialized and user authenticated") return this.loadKeycloakAuthorization() }).then(()=>{ this.#authorization = new KeycloakAuthorization(this.#keycloak) console.log("Keycloak authorization loaded and initialized", this.#authorization) this.unlock() }).catch(err=>{ console.error("Unable to initialize Keycloak",err) }) } loadKeycloak(){ return new Promise((resolve, reject)=>{ if (typeof Keycloak === 'undefined') { const script = document.createElement('script') script.src = this.#url + '/js/keycloak.js' script.type = 'text/javascript' script.addEventListener('load', resolve) document.head.appendChild(script) } else { resolve() } }) } initKeycloak(){ this.#keycloak = new Keycloak({ url: this.#url, realm: this.#realm, clientId: this.#clientId }) return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false }) } loadKeycloakAuthorization(){ return new Promise((resolve, reject)=>{ if (typeof KeycloakAuthorization === 'undefined') { const authz = document.createElement('script') authz.src = this.#url + '/js/keycloak-authz.js' authz.type = 'text/javascript' authz.addEventListener('load', resolve) document.head.appendChild(authz) } else { resolve() } }) } startStateChecker(){ this.#interval = window.setInterval(()=>{ if(this.#locked){ console.log("Still locked. Currently has " + this.#queue.length + " pending requests.") }else{ if(this.#queue.length > 0){ const audience = encodeURIComponent(this.#audience) console.log("Updating token") this.#keycloak.updateToken(30).then(()=>{ console.log("Checking entitlement") return this.#authorization.entitlement(audience) }).then( rpt => { console.log("Authorized") //transform all queued requests to fetches console.log("All pending requests to promises") let promises = this.#queue.map(r => { r.request.headers["Authorization"] = "Bearer " + rpt return r.resolve( fetch(r.url, r.request) ) }) //clear queue this.#queue = [] console.log("Resolving all fetches") return Promise.all(promises) } ).catch(err => alert("Unable to make calls: " + err)) } } }, 300) } parseJwt(token) { var base64Url = token.split('.')[1] var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) }).join('')) return JSON.parse(jsonPayload); } expirationDate(utc) { let d = new Date(0) d.setUTCSeconds(utc) return d } checkContext() { const parseJwt = this.parseJwt const expDt = this.expirationDate const audience = encodeURIComponent(this.#audience) this.#authorization.entitlement(audience).then(function (rpt) { // onGrant callback function. // If authorization was successful you'll receive an RPT // with the necessary permissions to access the resource server console.log(rpt) //console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) }) } secureFetch(url, request){ const p = new Promise((resolve, reject)=>{ let req = request ? request : {} if("headers" in req){ req.headers["Authorization"] = null } else { req.headers = { "Authorization" : null} } console.log("Queued request to url ", url) this.#queue.push({ url : url, request : req, resolve : resolve, reject : reject}) }) return p } download(url, name) { this.secureFetch(url).then(reply=>{ if(reply.status !== 200) throw "Unable to download"; return reply.blob() }).then(blob=>{ const objectURL = URL.createObjectURL(blob) var tmplnk = document.createElement("a") tmplnk.download = name tmplnk.href = objectURL document.body.appendChild(tmplnk) tmplnk.click() document.body.removeChild(tmplnk) }).catch(err=>alert(err) } logout() { if (this.#keycloak) { if (!this.#redirectUrl) { console.error("Missing required @redirectUrl attribute in d4s-boot") } else { this.#keycloak.logout({ redirectUri: this.#redirectUrl }) } } } static get observedAttributes() { return ["url", "realm", "gateway", "redirect-url", "context"]; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue) { switch (name) { case "url": this.#url = newValue break case "realm": this.#realm = newValue break case "gateway": this.#clientId = newValue break case "redirect-url": this.#redirectUrl = newValue break case "context": this.#audience = newValue break } } } get url() { return this.#url } set url(url) { this.#url = url this.setAttribute("url", url) } get realm() { return this.#realm } set realm(realm) { this.#realm = realm this.setAttribute("realm", realm) } get clientId() { return this.#clientId } set clientId(clientId) { this.#clientId = clientId this.setAttribute("gateway", clientId) } get redirectUrl() { return this.#redirectUrl } set redirectUrl(redirectUrl) { this.#redirectUrl = redirectUrl this.setAttribute("redirect-url", redirectUrl) } get context() { return this.#audience } set context(context) { this.#audience = context this.setAttribute("context", context) } }) /*connectedCallback() { if (typeof Keycloak === 'undefined') { const script = document.createElement('script') script.src = this.#url + '/js/keycloak.js' script.type = 'text/javascript' script.addEventListener('load', () => { this.initKeycloak() }) document.head.appendChild(script) } else { this.initKeycloak() } }*/ /*initKeycloak() { this.#keycloak = new Keycloak({ url: this.#url, realm: this.#realm, clientId: this.#clientId }) const keycloak = this.#keycloak const url = this.#url keycloak.init({ onLoad: 'login-required' , checkLoginIframe: false }).then((authenticated) => { console.log(authenticated ? 'Authenticated in ' + this.#realm : 'Not authenticated') if (typeof KeycloakAuthorization === 'undefined') { const authz = document.createElement('script') authz.src = url + '/js/keycloak-authz.js' authz.type = 'text/javascript' authz.addEventListener('load', () => { this.#authorization = new KeycloakAuthorization(keycloak) }) document.head.appendChild(authz) } else { this.#authorization = new KeycloakAuthorization(keycloak) } //console.log("expires: " + this.expirationDate(keycloak.tokenParsed.exp)) }).catch(function(err) { console.error("Failed to initialize d4science boot component") }) }*/ /*service(endpoint, method, params, onSuccess, onForbidden) { if (this.#authorization == null) { if (this.#attempts-- > 0) { setTimeout(() => this.service(endpoint, method, params, onSuccess, onForbidden), this.#timer) return } else { console.error("Impossible to initialize D4Science authorization component") throw "Fatal error" } } this.#keycloak.updateToken(30).then(() => { const audience = encodeURIComponent(this.#audience) this.#authorization.entitlement(audience).then(function (rpt) { var req = new XMLHttpRequest() req.open(method, endpoint, true) req.setRequestHeader('Accept', 'application/json') req.setRequestHeader('Authorization', 'Bearer ' + rpt) req.onreadystatechange = function () { if (req.readyState == 4) { if (req.status == 200) { onSuccess(req.response) } else if (req.status == 403) { onForbidden(req.statusText) } } } req.send(params) }) }).catch(function() { onForbidden('Failed to refresh token') }) }*/