/** runtime */ import 'regenerator-runtime'; /** Lit */ import { html, LitElement, css, unsafeCSS, TemplateResult, PropertyValueMap, nothing, } from 'lit'; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { customElement, property, state } from 'lit/decorators.js'; import { msg, str, updateWhenLocaleChanges } from '@lit/localize'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; /** Helpers */ // eslint-disable-next-line @typescript-eslint/no-unused-vars import sizeHelper from '@helpers/sizeHelper'; import { setLocale } from '@helpers/localisation'; import langHelper from '@helpers/langHelper'; /** Services */ import openIdConnect, { Response as OIDCResponse, } from '@uportal/open-id-connect'; import userInfoService, { userInfo } from '@services/userInfoService'; import orgInfoService, { orgInfo } from '@services/orgInfoService'; import templateService, { template } from '@services/templateService'; import sessionService from '@services/sessionService'; /** Libraries */ import debounce from 'lodash/debounce'; import throttle from 'lodash/throttle'; /** Components */ import '@gip-recia/eyebrow-user-info-lit'; import '@gip-recia/esco-content-menu-lit'; /** Styles */ import scss from '@styles/extended-uportal-header.scss'; /** Images */ import defaultOrgIcon from '@images/default-org.icon.png'; import defaultOrgImage from '@images/default-org.png'; import defaultAvatar from '@images/default-avatar.svg'; /** Icons */ import { icon } from '@fortawesome/fontawesome-svg-core'; import { faRightToBracket } from '@fortawesome/free-solid-svg-icons/faRightToBracket'; import portletService from '@services/portletService'; interface properties { messages: unknown; domain: string; portailPath: string; serviceName: string; favoritesPortletCardSize: string; gridPortletCardSize: string; defaultOrgLogoUrl: string; defaultOrgLogoPath: string; forceOrgLogoUrl: string; forceOrgLogoPath: string; defaultOrgIconUrl: string; defaultOrgIconPath: string; defaultAvatarUrl: string; defaultAvatarPath: string; contextApiUrl: string; favoriteApiUrl: string; layoutApiUrl: string; portletApiUrl: string; organizationApiUrl: string; userInfoApiUrl: string; sessionApiUrl: string; templateApiUrl: string; templateApiPath: string; template: template | null; signOutUrl: string; userInfoPortletUrl: string; switchOrgPortletUrl: string; switchOrgEvent: boolean; orgAttributeName: string; orgLogoUrlAttributeName: string; userAllOrgsIdAttributeName: string; hideActionMode: 'auto' | 'always' | 'never'; showFavoritesInSlider: boolean; iconType: | 'hamburger' | 'four-square' | 'four-empty-square' | 'nine-square' | 'nine-empty-square' | 'four-circle' | 'four-empty-circle' | 'nine-circle' | 'nine-empty-circle'; returnHomeTarget: string; returnHomeTitle: string | null; height: string; sessionRenewDisable: boolean; portletInfoApiUrl: string; fname: string; debug: boolean; } type overridableProperties = Omit< properties, 'templateApiUrl' | 'templateApiPath' | 'template' >; @customElement('extended-uportal-header') // eslint-disable-next-line @typescript-eslint/no-unused-vars export class ExtendedUportalHeader extends LitElement { @property({ type: Object }) messages = []; @property({ type: String }) domain = ''; @property({ type: String, attribute: 'portail-path' }) portailPath = ''; @property({ type: String, attribute: 'service-name' }) serviceName = ''; @property({ type: String, hasChanged(newVal: string) { return sizeHelper.validate(newVal, true, true); }, attribute: 'favorites-portlet-card-size', }) favoritesPortletCardSize = 'small'; @property({ type: String, hasChanged(newVal: string) { return sizeHelper.validate(newVal, true, true); }, attribute: 'grid-portlet-card-size', }) gridPortletCardSize = 'medium'; @property({ type: String, attribute: 'default-org-logo-url' }) defaultOrgLogoUrl = ''; @property({ type: String, attribute: 'default-org-logo-path' }) defaultOrgLogoPath = ''; @property({ type: String, attribute: 'force-org-logo-url' }) forceOrgLogoUrl = ''; @property({ type: String, attribute: 'force-org-logo-path' }) forceOrgLogoPath = ''; @property({ type: String, attribute: 'default-org-icon-url' }) defaultOrgIconUrl = ''; @property({ type: String, attribute: 'default-org-icon-path' }) defaultOrgIconPath = ''; @property({ type: String, attribute: 'default-avatar-url' }) defaultAvatarUrl = ''; @property({ type: String, attribute: 'default-avatar-path' }) defaultAvatarPath = ''; @property({ type: String, attribute: 'context-api-url' }) contextApiUrl = process.env.APP_PORTAL_CONTEXT ?? ''; @property({ type: String, attribute: 'favorite-api-url' }) favoriteApiUrl = (process.env.APP_PORTAL_CONTEXT ?? '') + (process.env.APP_FAVORITES_PORTLETS_URI ?? ''); @property({ type: String, attribute: 'layout-api-url' }) layoutApiUrl = (process.env.APP_PORTAL_CONTEXT ?? '') + (process.env.APP_FAVORITES_URI ?? ''); @property({ type: String, attribute: 'portlet-api-url' }) portletApiUrl = (process.env.APP_PORTAL_CONTEXT ?? '') + (process.env.APP_BROWSABLE_PORTLETS_URI ?? ''); @property({ type: String, attribute: 'organization-api-url' }) organizationApiUrl = (process.env.APP_PORTAL_CONTEXT ?? '') + (process.env.APP_ORG_API_URI ?? ''); @property({ type: String, attribute: 'user-info-api-url' }) userInfoApiUrl = (process.env.APP_PORTAL_CONTEXT ?? '') + (process.env.APP_USER_INFO_URI ?? ''); @property({ type: String, attribute: 'session-api-url' }) sessionApiUrl = (process.env.APP_PORTAL_CONTEXT ?? '') + (process.env.APP_SESSION_URI ?? ''); @property({ type: String, attribute: 'template-api-url' }) templateApiUrl = ''; @property({ type: String, attribute: 'template-api-path' }) templateApiPath = process.env.APP_TPL_API_PATH ?? ''; @property({ type: Object }) template: template | null = null; @property({ type: String, attribute: 'sign-out-url' }) signOutUrl = process.env.APP_LOGOUT_URL ?? ''; @property({ type: String, attribute: 'sign-in-url' }) signInUrl = ''; @property({ type: String, attribute: 'user-info-portlet-url' }) userInfoPortletUrl = ''; @property({ type: String, attribute: 'switch-org-portlet-url' }) switchOrgPortletUrl = ''; @property({ type: Boolean, attribute: 'switch-org-event' }) switchOrgEvent = false; @property({ type: String, attribute: 'user-org-id-attribute-name' }) orgAttributeName = 'ESCOSIRENCourant[0]'; @property({ type: String, attribute: 'org-logo-url-attribute-name' }) orgLogoUrlAttributeName = 'otherAttributes.ESCOStructureLogo[0]'; @property({ type: String, attribute: 'user-all-orgs-id-attribute-name' }) userAllOrgsIdAttributeName = 'ESCOSIREN'; @property({ type: String, hasChanged(newVal: string) { return ['auto', 'always', 'never'].includes(newVal); }, attribute: 'hide-action-mode', }) hideActionMode = 'auto'; @property({ type: Boolean, attribute: 'show-favorites-in-slider' }) showFavoritesInSlider = false; @property({ type: String, hasChanged(newVal: string) { return [ 'hamburger', 'four-square', 'four-empty-square', 'nine-square', 'nine-empty-square', 'four-circle', 'four-empty-circle', 'nine-circle', 'nine-empty-circle', ].includes(newVal); }, attribute: 'icon-type', }) iconType = 'hamburger'; @property({ type: String, attribute: 'return-home-target', hasChanged(newVal: string) { return ['_self', '_blank'].includes(newVal); }, }) returnHomeTarget = '_self'; @property({ type: String, attribute: 'return-home-title' }) returnHomeTitle: string | null = null; @property({ type: String }) height = 'auto'; @property({ type: Boolean, attribute: 'disable-session-renew' }) sessionRenewDisable = false; @property({ type: Object, attribute: 'dont-override' }) dontOverride: Array | null = null; @property({ type: String, attribute: 'portlet-info-api-url' }) portletInfoApiUrl = ''; @property({ type: String }) fname = ''; @property({ type: String, attribute: 'dnma-url' }) dnmaUrl = '/esciti/dnma/dnma.js'; @property({ type: Boolean }) debug = false; @state() private _loaded: number | boolean = false; @state() private _userApiResult: OIDCResponse | null = null; private _sessionTimeout = 300000; private _tokenExpireTime = 0; private _loadingToken = false; private _userInfos: userInfo | null = null; private _orgInfos: orgInfo | null = null; private _loadingData = false; private _loadingTemplate = false; hamburgerRef: Ref = createRef(); constructor() { super(); if (this.domain === '') { this.domain = window.document.domain; } const lhOpts = { languageCodeOnly: true, availableLanguages: ['fr', 'en'], defaultLanguage: 'en', }; const lang = langHelper.getPageLang(lhOpts); setLocale(lang); langHelper.setLocale(lang); updateWhenLocaleChanges(this); } connectedCallback(): void { super.connectedCallback(); ['mousemove', 'click', 'keypress'].every((event) => document.addEventListener(event, this._handleUserAction.bind(this)) ); window.addEventListener('eyebrow-user-info', (e: Event) => { if ((e as CustomEvent).detail.type === 'change-etab') { this.hamburgerRef.value?.dispatchEvent(new CustomEvent('switch-org')); } }); } disconnectedCallback(): void { super.disconnectedCallback(); ['mousemove', 'click', 'keypress'].every((event) => document.removeEventListener(event, this._handleUserAction.bind(this)) ); } protected shouldUpdate( // eslint-disable-next-line @typescript-eslint/no-explicit-any _changedProperties: PropertyValueMap | Map ): boolean { if (_changedProperties.has('messages')) { langHelper.setReference(this.messages); } if ( !this.template && !this._loadingTemplate && !this._loaded && !this._loadingData ) { this._firstLoad(); } if ( _changedProperties.has('userInfoApiUrl') || _changedProperties.has('layoutApiUrl') || _changedProperties.has('organizationApiUrl') ) { this._debounceLoad(); } if ( _changedProperties.has('template') || _changedProperties.has('templateApiUrl') || _changedProperties.has('templateApiPath') ) { this._debounceGetTemplate(); } if (!this._loaded) { return false; } return true; } private async _firstLoad() { await this._getTemplate(); this._loadPortletInformations(); this._debounceLoad(); this._initDnma(); } private _debounceLoad = debounce(this._load.bind(this), 500); private async _load() { this._loadingData = true; await this._renewToken(); const previusStatus = this._userInfos; this._userInfos = await userInfoService.get( this._makeUrl(this.userInfoApiUrl), this._makeUrl(this.layoutApiUrl), this.orgAttributeName, this.userAllOrgsIdAttributeName, this._userApiResult, this.debug ); if (this._userInfos?.orgId) { this._orgInfos = await orgInfoService.get( this._makeUrl(this.userInfoApiUrl), this._makeUrl(this.organizationApiUrl), this._userInfos.orgId, this._userApiResult, this.debug ); } this._loadingData = false; if (previusStatus != this._userInfos) { this._loaded = Date.now(); return; } this._loaded = true; } private _initDnma(): void { this.dnmaUrl += `?v=${new Date().getMonth()}-${new Date().getDay()}`; const { dnmaUrl, fname } = this; const data = { dnmaUrl, fname }; console.log('_initDnma', data); if (!document.querySelector('script#dnma')) { const dnmaScript = document.createElement('script'); dnmaScript.id = 'dnma'; dnmaScript.src = this._makeUrl(this.dnmaUrl); document.body.appendChild(dnmaScript); const dnmaSetupScript = document.createElement('script'); dnmaSetupScript.type = 'text/javascript'; dnmaSetupScript.text = ` try { if (ENT4DNMA) { ENT4DNMA.markPage('${fname}'); ENT4DNMA.markOnEvent('click-portlet-card'); } } catch (error) { console.info('DNMA is not available'); } `; setTimeout(() => { document.body.appendChild(dnmaSetupScript); }, 1000); } } private _debounceRenewToken = debounce(this._renewToken.bind(this), 500); private async _renewToken() { const currentTime = Math.ceil(Date.now() / 1000); if ( !this.debug && !this._loadingToken && currentTime >= this._tokenExpireTime ) { this._loadingToken = true; this._userApiResult = await openIdConnect({ userInfoApiUrl: this._makeUrl(this.userInfoApiUrl), }); this._tokenExpireTime = this._userApiResult.decoded.exp - 5 ?? 0; this._loadingToken = false; } } private _throttleRenewSession = throttle( this._renewSession.bind(this), this._sessionTimeout ); private async _renewSession() { if (this.sessionRenewDisable) { this._debounceLoad(); return; } const session = await sessionService.get(this._makeUrl(this.sessionApiUrl)); if (session !== null) { if (this._loaded && this._isConnected() && !session.isConnected) this._debounceLoad(); } } private _debounceGetTemplate = debounce(this._getTemplate.bind(this), 500); private async _getTemplate() { if (this.template !== null) return; this._loadingTemplate = true; this.template = await templateService.get(this._tplApiUrl(), this.domain); await this._overrideProperties(); this._loadingTemplate = false; } private _defaultProperties: properties = { messages: [], domain: '', portailPath: '', serviceName: '', favoritesPortletCardSize: 'small', gridPortletCardSize: 'medium', defaultOrgLogoUrl: '', defaultOrgLogoPath: '', forceOrgLogoUrl: '', forceOrgLogoPath: '', defaultOrgIconUrl: '', defaultOrgIconPath: '', defaultAvatarUrl: '', defaultAvatarPath: '', contextApiUrl: '', favoriteApiUrl: '', layoutApiUrl: '', portletApiUrl: '', organizationApiUrl: '', userInfoApiUrl: '', sessionApiUrl: '', templateApiUrl: '', templateApiPath: '', template: null, signOutUrl: '', userInfoPortletUrl: '', switchOrgPortletUrl: '', switchOrgEvent: false, orgAttributeName: 'ESCOSIRENCourant[0]', orgLogoUrlAttributeName: 'otherAttributes.ESCOStructureLogo[0]', userAllOrgsIdAttributeName: 'ESCOSIREN', hideActionMode: 'auto', showFavoritesInSlider: false, iconType: 'hamburger', returnHomeTarget: '_self', returnHomeTitle: null, height: 'auto', sessionRenewDisable: false, portletInfoApiUrl: '', fname: '', debug: false, }; private async _overrideProperties(): Promise { let config = this.template?.config; if (!config) { return; } config = Object.fromEntries( Object.entries(config).map(([key, value]) => [ key.replace(/-./g, (m) => m.toUpperCase()[1]), value, ]) ); for (const [key, value] of Object.entries(config)) { const currentValue = this[key as keyof overridableProperties]; const defaultValue = this._defaultProperties[key as keyof overridableProperties]; const keepCurrent = !!this.dontOverride?.includes( key as keyof overridableProperties ); let override = false; switch (typeof currentValue) { case 'object': if ( Array.isArray(currentValue) && Array.isArray(defaultValue) && currentValue.every((val) => defaultValue.includes(val)) ) override = true; else if (defaultValue === null) { if (currentValue === defaultValue || currentValue.length === 0) override = true; } break; default: if (currentValue === defaultValue || currentValue === '') override = true; break; } this.debug && console.log({ key, current: { value: currentValue, type: typeof currentValue }, default: { value: defaultValue, type: typeof defaultValue }, received: { value, type: typeof value }, override, keepCurrent, }); if (override && !keepCurrent) { this[key as keyof overridableProperties] = value as never; if (key === 'messages') langHelper.setReference(this.messages); } } } private async _loadPortletInformations() { if ( this.portletInfoApiUrl === '' || this.fname === '' || this.serviceName !== '' ) return; const data = await portletService.get( this._makeUrl(this.portletInfoApiUrl), this.fname ); if (data) this.serviceName = data.title; } private _handleUserAction() { if (this._loaded) { this._debounceRenewToken(); if (!this.sessionRenewDisable) this._throttleRenewSession(); } } private _makeUrl(path: string, domain = ''): string { if (path.startsWith('http')) return path; const protocol = this.debug ? 'http' : 'https'; return `${protocol}://${domain == '' ? this.domain : domain}${path}`; } private _defaultOrgLogo() { return this.defaultOrgLogoUrl != '' ? this.defaultOrgLogoUrl : this.defaultOrgLogoPath != '' ? this._makeUrl(this.defaultOrgLogoPath) : defaultOrgImage; } private _forceOrgLogo() { return this.forceOrgLogoUrl != '' ? this.forceOrgLogoUrl : this.forceOrgLogoPath != '' ? this._makeUrl(this.forceOrgLogoPath) : false; } private _picture() { return this._userInfos?.picture ? this._makeUrl(this._userInfos.picture) : this.defaultAvatarUrl != '' ? this.defaultAvatarUrl : this.defaultAvatarPath != '' ? this._makeUrl(this.defaultAvatarPath) : defaultAvatar; } private _orgIconUrl() { return this.template?.iconPath ? this._makeUrl(this.template?.iconPath) : this.defaultOrgIconUrl != '' ? this.defaultOrgIconUrl : this.defaultOrgIconPath != '' ? this._makeUrl(this.defaultOrgIconPath) : defaultOrgIcon; } private _tplApiUrl() { return this.templateApiUrl != '' ? this.templateApiUrl : this.templateApiPath != '' ? this._makeUrl(this.templateApiPath) : ''; } private _isConnected(): boolean { return this._userInfos !== null && this._orgInfos !== null; } render(): TemplateResult { this._preRender(); return this._isConnected() || this.signInUrl !== '' ? html`
${this._renderMenu()}

${this._orgInfos?.displayName.toUpperCase()} ${this.serviceName != '' ? html` > ${this.serviceName}` : ''}

${this._renderUser()}
` : html` `; } private _preRender() { if (this.template?.color) { this.style.setProperty( '--ext-header-tpl-primary-color', this.template?.color ); } if (this.template?.iconOpacity) { this.style.setProperty( '--ext-header-tpl-icon-opacity', this.template.iconOpacity.toString() ); } if (this.height !== 'auto') { this.style.setProperty('--ext-header-tpl-height', this.height); } } private _renderMenu() { const userApiResult = this._userApiResult ? JSON.stringify(this._userApiResult) : nothing; return this._userInfos && this._orgInfos ? html`
` : html``; } private _renderUser() { const signInIcon = `${icon(faRightToBracket).html}`; return this._userInfos && this._orgInfos ? html`
` : html``; } static styles = css` ${unsafeCSS(scss)} `; }