@kano/web-components
Version:
Shared web-components for the Kano ecosystem.
221 lines (200 loc) • 6.81 kB
JavaScript
import '@polymer/polymer/polymer-legacy.js';
import '@polymer/app-storage/app-localstorage/app-localstorage-document.js';
import { APIClient } from '../kano-api-client-behavior/kano-api-client-behavior.js';
import { Polymer } from '@polymer/polymer/lib/legacy/polymer-fn.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
const MAX_SESSION_LENGTH = 86400;
// Copies the legacy KW token to the v2 in localStorage
var legacyToken = localStorage.getItem('KW_TOKEN');
if (!!legacyToken && legacyToken !== 'null') {
localStorage.setItem('KW_TOKENv2', JSON.stringify(legacyToken));
}
// Promise map of the requests that will be made. This allows all instances of `kano-session` to share requests
var promises = {};
Polymer({
_template: html`
<app-localstorage-document id="session" key="KW_SESSION" data="{{session}}"></app-localstorage-document>
<app-localstorage-document id="token" key="KW_TOKENv2" data="{{token}}"></app-localstorage-document>
`,
is: 'kano-session',
behaviors: [APIClient],
properties: {
/**
* Authenticated user. Will be undefined if no token was retrieved from the storage
*/
user: {
type: Object,
notify: true
},
/**
* User's token. Used to retrieve the session if not cached or expired. Stored in the storage.
*/
token: {
type: Object,
notify: true
},
/**
* Set to true to enforce the presence of the `profile` key in the user. Will be cached in memory for
* the durartion of the navigation
*/
includeProfile: {
type: Boolean,
value: false
},
/**
* Exposes the status of the session. `initialising` ,`not-authenticated`, `authenticated`, `authenticating`, `expired`
*/
status: {
type: String,
value: 'initialising',
notify: true
}
},
observers: [
'_sessionChanged(session)',
'_tokenChanged(token)',
'_userChanged(user)',
'_includeProfileChanged(includeProfile, session, token)'
],
attached () {
this.init();
},
init () {
return Promise.all([
this.$.token.initializeStoredValue(),
this.$.session.initializeStoredValue()
]).then(results => {
this.token = this.token || null;
this.session = this.session || null;
if (!this.token) {
this.status = 'not-authenticated';
}
return Promise.resolve(this.session);
});
},
_tokenChanged (token) {
if (!this.session || this._hasExpired()) {
this._retrieveSession();
} else {
this.set('user', this.session.user);
this.status = 'authenticated';
}
if (!token) {
localStorage.removeItem('KW_TOKEN');
this.set('user', null);
} else {
localStorage.setItem('KW_TOKEN', this.token);
}
},
_includeProfileChanged (value, session, token) {
if (value && session && token) {
if (!promises['profile']) {
var headers = new Headers();
headers.append('Authorization', this.token);
promises['profile'] = fetch(this._getUrl('user-by-username', { username: session.user.username }), { headers })
.then(r => r.json())
.catch(e => {
let offline = !window.navigator.onLine;
/**
* We should only clear the token and session if
* the user is online. Otherwise it's a
* connection error, and we can leave the token
* in place. If the browser doesn't support
* `window.navigator.onLine`, then this will be
* false to avoid errors.
*/
if (!offline) {
this.token = null;
this.session = null;
this.status = 'not-authenticated';
}
/** Clear the promise so we can try again */
promises['profile'] = null;
});
}
promises['profile'].then(res => {
if (res) {
this.set('user.profile', res.user.profile);
this.updateProgress();
}
});
}
},
_hasExpired () {
return (new Date() - this.session.startedAt) > MAX_SESSION_LENGTH;
},
logout () {
localStorage.setItem('progress', null);
this.set('user', null);
this.set('token', null);
promises = {};
this.fire('logout');
},
reinitialize () {
return this.init().then(session => {
if (!this.token) {
return Promise.reject();
}
var headers = new Headers();
headers.append('Authorization', this.token);
if (!promises['session']) {
this.status = 'authenticating';
promises['session'] = fetch(this._getUrl('session'), { headers })
.then(r => r.json());
}
return promises['session'].then(res => {
this.set('user', res.session.user);
this.status = 'authenticated';
return Promise.resolve(res.session.user);
});
});
},
_retrieveSession () {
if (!this.token) {
return;
}
var headers = new Headers();
headers.append('Authorization', this.token);
if (!promises['session']) {
this.status = 'authenticating';
promises['session'] = fetch(this._getUrl('session'), { headers })
.then(r => r.json());
}
promises['session'].then(res => {
this.set('user', res.session.user);
this.status = 'authenticated';
});
},
_sessionChanged (session) {
if (session === null) {
this.set('user', null);
}
},
updateProgress () {
if (!promises['progress']) {
var headers = new Headers();
headers.append('Authorization', this.token);
promises['progress'] = fetch(this._getUrl('progress'), {
headers
})
.then(r => r.json())
.catch(e => {
/** Clear the promise to that we can try again */
promises['progress'] = null;
});
}
promises['progress'].then(res => {
this.set('user.profile.progress', res.progress);
});
},
_userChanged (user) {
if (user) {
this.set('session', {
user,
startedAt: Date.now()
});
} else {
this.set('session', null);
}
}
});