@openremote/core 1.8.0-snapshot.20250725074716 → 1.8.0-snapshot.20250725120000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -105
- package/dist/umd/index.bundle.js.map +1 -1
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/index.orbundle.js +49 -49
- package/dist/umd/index.orbundle.js.map +1 -1
- package/lib/asset-mixin.js +161 -1
- package/lib/console.js +541 -1
- package/lib/defaults.js +16 -1
- package/lib/event.js +588 -1
- package/lib/index.js +1003 -1
- package/lib/util.js +1000 -1
- package/package.json +4 -4
package/lib/index.js
CHANGED
|
@@ -1 +1,1003 @@
|
|
|
1
|
-
var __awaiter=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(o,r){function s(e){try{l(n.next(e))}catch(e){r(e)}}function a(e){try{l(n.throw(e))}catch(e){r(e)}}function l(e){var t;e.done?o(e.value):((t=e.value)instanceof i?t:new i(function(e){e(t)})).then(s,a)}l((n=n.apply(e,t||[])).next())})};import"url-search-params-polyfill";import{Console as e}from"./console";import t from"@openremote/rest";import{EventProviderStatus as i,WebSocketEventProvider as n}from"./event";import o from"i18next";import r from"i18next-http-backend";import s from"moment";import{AssetModelUtil as a}from"@openremote/model";import*as l from"./util";import{createMdiIconSet as c,createSvgIconSet as h,IconSets as d,OrIconSet as u}from"@openremote/or-icon";import _ from"keycloak-js";export*from"./asset-mixin";export*from"./console";export*from"./event";export*from"./defaults";export const DEFAULT_ICONSET="mdi";export const OPENREMOTE_CLIENT_ID="openremote";export const RESTRICTED_USER_REALM_ROLE="restricted_user";export var ORError;!function(e){e.MANAGER_FAILED_TO_LOAD="MANAGER_FAILED_TO_LOAD",e.AUTH_FAILED="AUTH_FAILED",e.AUTH_TYPE_UNSUPPORTED="AUTH_TYPE_UNSUPPORTED",e.CONSOLE_ERROR="CONSOLE_INIT_ERROR",e.EVENTS_CONNECTION_ERROR="EVENTS_CONNECTION_ERROR",e.TRANSLATION_ERROR="TRANSLATION_ERROR"}(ORError||(ORError={}));export var OREvent;!function(e){e.ERROR="ERROR",e.READY="READY",e.ONLINE="ONLINE",e.OFFLINE="OFFLINE",e.CONNECTING="CONNECTING",e.CONSOLE_INIT="CONSOLE_INIT",e.CONSOLE_READY="CONSOLE_READY",e.TRANSLATE_INIT="TRANSLATE_INIT",e.TRANSLATE_LANGUAGE_CHANGED="TRANSLATE_LANGUAGE_CHANGED",e.DISPLAY_REALM_CHANGED="DISPLAY_REALM_CHANGED"}(OREvent||(OREvent={}));export const DEFAULT_LANGUAGES={en:"english",cn:"chinese",nl:"dutch",fr:"french",de:"german",it:"italian",pt:"portuguese",ro:"romanian",es:"spanish",uk:"ukrainian"};export function normaliseConfig(e){let t=e?Object.assign({},e):{};return t.managerUrl&&""!==t.managerUrl?t.managerUrl=t.managerUrl.replace(/\/+$/,""):t.managerUrl=window.location.protocol+"//"+window.location.hostname+(window.location.port?":"+window.location.port:"")+window.location.pathname.replace(/\/[^/]+\/?$/,""),t.realm&&""!==t.realm||(t.realm="master"),t.auth||(t.auth="KEYCLOAK"),void 0===t.consoleAutoEnable&&(t.consoleAutoEnable=!0),void 0===t.applyConfigToAdmin&&(t.applyConfigToAdmin=!0),t.eventProviderType||(t.eventProviderType="WEBSOCKET"),(!t.pollingIntervalMillis||t.pollingIntervalMillis<5e3)&&(t.pollingIntervalMillis=1e4),void 0===t.loadIcons&&(t.loadIcons=!0),void 0===t.loadTranslations&&(t.loadTranslations=["or"]),void 0===t.translationsLoadPath&&(t.translationsLoadPath="locales/{{lng}}/{{ns}}.json"),void 0===t.loadDescriptors&&(t.loadDescriptors=!0),void 0===t.clientId&&(t.clientId=OPENREMOTE_CLIENT_ID),t}export class Manager{constructor(){this._authenticated=!1,this._ready=!1,this._name="",this._username="",this._keycloakUpdateTokenInterval=void 0,this._managerVersion="",this._authServerUrl="",this._listeners=[],this._disconnected=!1}get username(){return this._username}get error(){return this._error}get authenticated(){return this._authenticated}get ready(){return this._ready}get config(){return this._config}get roles(){let e=new Map;return this._keycloak?this._keycloak.resourceAccess&&this._config.clientId&&this._keycloak.resourceAccess&&Object.entries(this._keycloak.resourceAccess).forEach(([t,i])=>{let n=i.roles;e.set(t,n)}):this._basicIdentity&&this._basicIdentity.roles&&e.set(this._config.clientId,this._basicIdentity.roles),e}get managerVersion(){return this._managerVersion}get isManagerAvailable(){return this._managerVersion&&""!==this._managerVersion}get managerUrl(){var e;return null==(e=this._config)?void 0:e.managerUrl}get keycloakUrl(){var e;return null==(e=this._config)?void 0:e.keycloakUrl}get isError(){return!!this._error}get connectionStatus(){return this._events&&this._events.status}get console(){return this._console}get consoleAppConfig(){return this._consoleAppConfig}get events(){return this._events}get rest(){return t}get language(){return o.language}set language(e){console.debug(`Changing language to ${e}.`),e&&(o.changeLanguage(e),this.console.storeData("LANGUAGE",e),this.authenticated&&this.updateKeycloakUserLanguage(e).catch(e=>console.error(e)))}get displayRealm(){return this._displayRealm||this._config.realm}set displayRealm(e){this.isSuperUser()&&this._displayRealm!==e&&(this._displayRealm=e,this._emitEvent(OREvent.DISPLAY_REALM_CHANGED))}get clientId(){return this._config.clientId||OPENREMOTE_CLIENT_ID}getEventProvider(){return this.events}get mapType(){return this._config.mapType||"VECTOR"}isManagerSameOrigin(){if(!this.ready)return!1;let e=new URL(this._config.managerUrl),t=window.location;return e.protocol===t.protocol&&e.hostname===t.hostname&&e.port===t.port}addListener(e){0>this._listeners.indexOf(e)&&this._listeners.push(e)}removeListener(e){let t=this._listeners.indexOf(e);t>=0&&this._listeners.splice(t,1)}init(t){return __awaiter(this,void 0,void 0,function*(){this._config&&console.debug("Already initialised"),this._config=normaliseConfig(t);let i=yield this.loadManagerInfo(),n=new e(this._config.realm,this._config.consoleAutoEnable,()=>{this._emitEvent(OREvent.CONSOLE_READY)});if(this._console=n,"BASIC"===this._config.auth)i=(yield this.doTranslateInit())&&i,i=yield this.doAuthInit();else if("KEYCLOAK"===this._config.auth){if(!this._config.keycloakUrl&&this._authServerUrl){let e,t=new URL(this._config.managerUrl);this._authServerUrl.startsWith("//")&&(this._authServerUrl=t.protocol+this._authServerUrl);try{e=new URL(this._authServerUrl)}catch(i){(e=new URL(t)).pathname=this._authServerUrl}e.protocol||(e.protocol=t.protocol),e.hostname||(e.hostname=t.hostname),e.port||(e.port=t.port),this._config.keycloakUrl=e.toString()}this._config.keycloakUrl||(this._config.keycloakUrl=this._config.managerUrl+"/auth"),this._config.keycloakUrl=this._config.keycloakUrl.replace(/\/+$/,""),(i=yield this.doAuthInit())||this._config.skipFallbackToBasicAuth||(console.debug("Falling back to basic auth"),this._config.auth="BASIC",i=yield this.doAuthInit())}return!!i&&(i&&(i=this.doRestApiInit()),(yield this.doConsoleInit())&&this._clearWebHistory(),(i=(yield this.doTranslateInit())&&i)&&(i=yield this.doDescriptorsInit(),i=yield this.getConsoleAppConfig()),this.doIconInit(),i?(this._readyCallback&&(yield this._readyCallback()),this._ready=!0,this._emitEvent(OREvent.READY)):(this._config=void 0,console.warn("Failed to initialise the manager")),this.displayRealm=t.realm||"master",i)})}loadManagerInfo(){return __awaiter(this,void 0,void 0,function*(){try{let e=yield new Promise((e,t)=>{let i=new XMLHttpRequest;i.addEventListener("load",()=>{e(JSON.parse(i.responseText))}),i.addEventListener("error",()=>{t(Error("Failed to contact the manager"))}),i.open("GET",this._config.managerUrl+"/api/master/info"),i.send()});return this._managerVersion=e&&e.version?e.version:"",this._authServerUrl=e&&e.authServerUrl?e.authServerUrl:"",!0}catch(e){return console.error("Failed to contact the manager",e),this._setError(ORError.MANAGER_FAILED_TO_LOAD),!1}})}doTranslateInit(){return __awaiter(this,void 0,void 0,function*(){if(o.isInitialized)return!0;o.on("initialized",e=>{this._emitEvent(OREvent.TRANSLATE_INIT)}),o.on("languageChanged",e=>{s.locale(e),this._emitEvent(OREvent.TRANSLATE_LANGUAGE_CHANGED)});let e={lng:(yield this.getConsolePreferredLanguage())||(yield this.getUserPreferredLanguage())||this.config.defaultLanguage||"en",fallbackLng:"en",defaultNS:"app",fallbackNS:"or",ns:this.config.loadTranslations,interpolation:{format:(e,t,i)=>"uppercase"===t?e.toUpperCase():e instanceof Date?s(e).format(t):e},backend:{loadPath:(e,t)=>1===t.length&&"or"===t[0]?this.config.managerUrl+"/shared/locales/{{lng}}/{{ns}}.json":this.config.translationsLoadPath?this.config.translationsLoadPath:"locales/{{lng}}/{{ns}}.json"}};this.config.configureTranslationsOptions&&this.config.configureTranslationsOptions(e);try{yield o.use(r).init(e)}catch(e){return console.error(e),this._setError(ORError.TRANSLATION_ERROR),!1}return!0})}doDescriptorsInit(){return __awaiter(this,void 0,void 0,function*(){if(!this.config.loadDescriptors)return!0;try{let e=yield t.api.AssetModelResource.getAssetInfos(),i=yield t.api.AssetModelResource.getMetaItemDescriptors(),n=yield t.api.AssetModelResource.getValueDescriptors();a._assetTypeInfos=e.data,a._metaItemDescriptors=Object.values(i.data),a._valueDescriptors=Object.values(n.data)}catch(e){return console.error(e),!1}return!0})}doAuthInit(){return __awaiter(this,void 0,void 0,function*(){let e=!0;switch(this._config.auth){case"BASIC":e=yield this.initialiseBasicAuth();break;case"KEYCLOAK":e=yield this.loadAndInitialiseKeycloak();break;case"NONE":return!0;default:return this._setError(ORError.AUTH_TYPE_UNSUPPORTED),!1}return!!e&&(t.addRequestInterceptor(e=>{if(!e.headers.Authorization){let t=this.getAuthorizationHeader();t&&(e.headers.Authorization=t)}return e}),e)})}doRestApiInit(){return t.setTimeout(2e4),t.initialise(this.getApiBaseUrl()),!0}doEventsSubscriptionInit(){return __awaiter(this,void 0,void 0,function*(){let e=!1;return"WEBSOCKET"===this._config.eventProviderType&&(this._events=new n(this._config.managerUrl),this._events.subscribeStatusChange(e=>this._onEventProviderStatusChanged(e)),e=yield this._events.connect()),e||this._setError(ORError.EVENTS_CONNECTION_ERROR),e})}_connectEvents(){var e;(null==(e=this.events)?void 0:e.status)===i.DISCONNECTED?this.events.connect().catch(e=>{console.error("Failed to connect EventProvider."),console.error(e)}):console.warn("Tried to connect EventProvider, but it wasn't disconnected!")}_onEventProviderStatusChanged(e){switch(e){case i.DISCONNECTED:this._onDisconnect();break;case i.CONNECTED:break;case i.CONNECTING:this._emitEvent(OREvent.CONNECTING)}}doConsoleInit(){return __awaiter(this,void 0,void 0,function*(){try{return yield this.console.initialise(),this._emitEvent(OREvent.CONSOLE_INIT),!0}catch(e){return this._setError(ORError.CONSOLE_ERROR),!1}})}doIconInit(){this._config.loadIcons&&(d.addIconSet("mdi",c(manager.managerUrl)),d.addIconSet("or",h(u.size,u.icons)))}getConsoleAppConfig(){return __awaiter(this,void 0,void 0,function*(){var e;try{let t=yield fetch((null!=(e=manager.managerUrl)?e:"")+"/consoleappconfig/"+manager.displayRealm+".json");return this._consoleAppConfig=yield t.json(),!0}catch(e){return!0}})}getConsolePreferredLanguage(){return __awaiter(this,arguments,void 0,function*(e=this.console){return e.retrieveData("LANGUAGE")})}getUserPreferredLanguage(){return __awaiter(this,arguments,void 0,function*(e=this._keycloak){if(e&&e.authenticated){let t=(null==e?void 0:e.profile)||(yield null==e?void 0:e.loadUserProfile());if(null==t?void 0:t.attributes){let e=new Map(Object.entries(t.attributes));if(e.has("locale")){let t=e.get("locale");if("string"==typeof t[0])return t[0]}console.warn("Could not get user language from keycloak: no user attributes were found.")}else console.warn("Could not get user language from keycloak: no valid keycloak user profile was found.")}})}updateKeycloakUserLanguage(e){return __awaiter(this,arguments,void 0,function*(e,t=this.rest){return this.authenticated?t?void(yield t.api.UserResource.updateCurrentUserLocale(e,{headers:{"Content-Type":"application/json"}})):void console.warn("Tried updating user language, but the REST API is not initialized yet."):void console.warn("Tried updating user language, but the user is not authenticated.")})}logout(e){this._authenticated&&(this._authenticated=!0,this._keycloak?(this.isMobile()&&this.console.storeData("REFRESH_TOKEN",null),this._keycloakUpdateTokenInterval&&(window.clearTimeout(this._keycloakUpdateTokenInterval),this._keycloakUpdateTokenInterval=void 0),this._keycloak.logout(e&&""!==e?{redirectUri:e}:void 0)):this._basicIdentity&&(this._basicIdentity=void 0,e?window.location.href=e:window.location.reload()))}login(e){switch(this._config.auth){case"BASIC":e&&e.credentials&&(this._config.credentials=Object.assign({},e.credentials)),this.doBasicLogin();break;case"KEYCLOAK":if(this._keycloak){let t={};e&&e.redirectUrl&&""!==e.redirectUrl&&(t.redirectUri=e.redirectUrl),(null==e?void 0:e.action)&&""!==e.action&&(t.action=e.action),this.isMobile()&&(t.scope="offline_access"),this._keycloak.login(t)}}}initialiseBasicAuth(){return __awaiter(this,void 0,void 0,function*(){return this.config.basicLoginProvider?(this.config.autoLogin&&(this._readyCallback=()=>this.doBasicLogin()),!0):(console.debug("No basicLoginProvider defined on config so cannot display login UI"),!1)})}doBasicLogin(){return __awaiter(this,void 0,void 0,function*(){var e,i,n,o;if(!this.config.basicLoginProvider)return;let r={username:(null==(e=this.config.credentials)?void 0:e.username)?null==(i=this.config.credentials)?void 0:i.username:"",password:(null==(n=this.config.credentials)?void 0:n.password)?null==(o=this.config.credentials)?void 0:o.password:"",cancel:!1},s=!1;for(this._basicIdentity={roles:void 0,token:void 0,user:void 0};!s;){if((r=yield this.config.basicLoginProvider(r.username,r.password)).cancel){console.debug("Basic authentication cancelled by user");break}if(!r.username||!r.password)continue;this._basicIdentity.token=btoa(r.username+":"+r.password);let e=!1;try{let i=yield t.api.UserResource.getCurrent();200===i.status&&(e=!0,this._basicIdentity.user=i.data),e||401!==i.status&&403!==i.status||console.debug("Basic authentication invalid credentials, trying again")}catch(e){console.error("Basic auth failed: ",e)}if(e){console.debug("Basic authentication successful"),s=!0;let e=yield t.api.UserResource.getCurrentUserClientRoles(this.clientId);this._basicIdentity.roles=e.data}else{console.debug("Unknown response so aborting"),this._basicIdentity=void 0;break}}s&&this._onAuthenticated()})}isSuperUser(){return!!(this.getRealm()&&"master"===this.getRealm()&&this.hasRealmRole("admin"))}isRestrictedUser(){return!!this.hasRealmRole("restricted_user")}getApiBaseUrl(){let e=this._config.managerUrl;return e+("/api/"+this._config.realm+"/")}getAppName(){let e=location.pathname.split("/");return e.length>=1?e[1]:""}hasRealmRole(e){return this.isKeycloak()&&this._keycloak.hasRealmRole(e)}hasRole(e,t=this._config.clientId){let i=this.roles;return i&&i.has(t)&&i.get(t).indexOf(e)>=0}getAuthorizationHeader(){return this.getKeycloakToken()?"Bearer "+this.getKeycloakToken():this.getBasicToken()?"Basic "+this.getBasicToken():void 0}getKeycloakToken(){if(this.isKeycloak())return this._keycloak.token}getBasicToken(){return this._basicIdentity?this._basicIdentity.token:void 0}getRealm(){if(this._config)return this._config.realm}isMobile(){return this.console&&this.console.isMobile}isKeycloak(){return!!this._keycloak}_onAuthenticated(){this._authenticated=!0,this._events||this.doEventsSubscriptionInit()}loadAndInitialiseKeycloak(){return __awaiter(this,void 0,void 0,function*(){var e,t,i,n;try{this._keycloak=new _({clientId:this._config.clientId,realm:this._config.realm,url:this._config.keycloakUrl});let o=yield this._getNativeOfflineRefreshToken(),r=yield this._keycloak.init({checkLoginIframe:!1,onLoad:"check-sso"});if(!r&&o)try{console.error("SETTING OFFLINE TOKEN"),this._keycloak.refreshToken=o,r=yield this._updateKeycloakAccessToken()}catch(e){console.error("Failed to authenticate using offline token")}if(r)this._name=null==(e=this._keycloak.tokenParsed)?void 0:e.name,this._username=null==(t=this._keycloak.tokenParsed)?void 0:t.preferred_username,this._createTokenUpdateInterval(),this.isMobile()&&(null==(n=null==(i=this._keycloak)?void 0:i.refreshTokenParsed)?void 0:n.typ)==="Offline"&&(console.debug("Storing offline refresh token"),this.console.storeData("REFRESH_TOKEN",this._keycloak.refreshToken)),this._onAuthenticated();else if(this.config.autoLogin)return this.login(),!1;return!0}catch(e){return this._authenticated=!1,this._setError(ORError.AUTH_FAILED),console.error("Failed to initialise Keycloak: "+e),!1}})}_createTokenUpdateInterval(){this._keycloakUpdateTokenInterval||(this._keycloakUpdateTokenInterval=window.setInterval(()=>__awaiter(this,void 0,void 0,function*(){yield this._updateKeycloakAccessToken().catch(()=>{console.debug("Keycloak failed to refresh the access token"),this._onDisconnect()})}),1e4))}_updateKeycloakAccessToken(){return __awaiter(this,void 0,void 0,function*(){let e=yield this._keycloak.updateToken(20);return console.debug("Access token update success, refreshed from server: "+e),e&&this._onAuthenticated(),e})}_getNativeOfflineRefreshToken(){return __awaiter(this,void 0,void 0,function*(){if(this.isMobile())return yield this.console.retrieveData("REFRESH_TOKEN")})}_emitEvent(e){window.setTimeout(()=>{for(let t of this._listeners)t(e)},0)}_setError(e){this._error=e,this._emitEvent(OREvent.ERROR),console.warn("Error set: "+e)}_clearWebHistory(){var e;null==(e=this.console)||e._doSendGenericMessage("CLEAR_WEB_HISTORY",void 0)}_onDisconnect(){return __awaiter(this,void 0,void 0,function*(){this._disconnected||(console.debug("Disconnected"),this._disconnected=!0,this._keycloakUpdateTokenInterval&&(window.clearTimeout(this._keycloakUpdateTokenInterval),this._keycloakUpdateTokenInterval=void 0),this._emitEvent(OREvent.OFFLINE),this.reconnect())})}_onReconnected(){console.debug("Reconnected"),this._disconnected=!1,this._createTokenUpdateInterval(),this._emitEvent(OREvent.ONLINE)}reconnect(){return __awaiter(this,arguments,void 0,function*(e=3e3){if(!this._disconnected)return;this._reconnectTimer&&(window.clearTimeout(this._reconnectTimer),this._reconnectTimer=void 0);let t=yield __awaiter(this,void 0,void 0,function*(){var e;if(console.debug("Attempting reconnect"),!(yield this.isKeycloakReachable()))return console.debug("Keycloak is unreachable"),!1;console.debug("Keycloak is reachable"),console.debug("Checking keycloak access token");try{yield this._updateKeycloakAccessToken()}catch(t){let e=yield this._getNativeOfflineRefreshToken();this._keycloak.refreshToken=e;try{yield this._updateKeycloakAccessToken()}catch(e){console.debug("Cannot update access token so sending to login"),this.login();return}return console.debug("Keycloak access token is valid"),!0}let t=this.events&&this.events.status===i.CONNECTING;return console.debug("If event provider offline then attempting reconnect: offline="+t),!t||(yield null==(e=this.events)?void 0:e.connect())});if(void 0!==t){if(!t){console.debug("Scheduling another reconnect attempt in (ms): "+(e=Math.min(Manager.MAX_RECONNECT_DELAY,e+3e3))),this._reconnectTimer=window.setTimeout(()=>this.reconnect(e),e);return}this._onReconnected()}})}isKeycloakReachable(){return __awaiter(this,arguments,void 0,function*(e=2e3){let t=new AbortController,i=setTimeout(()=>t.abort(),e);try{let e=this._keycloak.endpoints.token(),i=yield fetch(e,{method:"OPTIONS",signal:t.signal});return 200===i.status}catch(e){return!1}finally{clearTimeout(i)}})}}Manager.MAX_RECONNECT_DELAY=45e3;export const manager=new Manager;export default manager;export{l as Util};
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import "url-search-params-polyfill";
|
|
11
|
+
import { Console } from "./console";
|
|
12
|
+
import rest from "@openremote/rest";
|
|
13
|
+
import { EventProviderStatus, WebSocketEventProvider } from "./event";
|
|
14
|
+
import i18next from "i18next";
|
|
15
|
+
import i18nextBackend from "i18next-http-backend";
|
|
16
|
+
import moment from "moment";
|
|
17
|
+
import { AssetModelUtil } from "@openremote/model";
|
|
18
|
+
import * as Util from "./util";
|
|
19
|
+
import { createMdiIconSet, createSvgIconSet, IconSets, OrIconSet } from "@openremote/or-icon";
|
|
20
|
+
import Keycloak from 'keycloak-js';
|
|
21
|
+
// Re-exports
|
|
22
|
+
export { Util };
|
|
23
|
+
export * from "./asset-mixin";
|
|
24
|
+
export * from "./console";
|
|
25
|
+
export * from "./event";
|
|
26
|
+
export * from "./defaults";
|
|
27
|
+
export const DEFAULT_ICONSET = "mdi";
|
|
28
|
+
export const OPENREMOTE_CLIENT_ID = "openremote";
|
|
29
|
+
export const RESTRICTED_USER_REALM_ROLE = "restricted_user";
|
|
30
|
+
export var ORError;
|
|
31
|
+
(function (ORError) {
|
|
32
|
+
ORError["MANAGER_FAILED_TO_LOAD"] = "MANAGER_FAILED_TO_LOAD";
|
|
33
|
+
ORError["AUTH_FAILED"] = "AUTH_FAILED";
|
|
34
|
+
ORError["AUTH_TYPE_UNSUPPORTED"] = "AUTH_TYPE_UNSUPPORTED";
|
|
35
|
+
ORError["CONSOLE_ERROR"] = "CONSOLE_INIT_ERROR";
|
|
36
|
+
ORError["EVENTS_CONNECTION_ERROR"] = "EVENTS_CONNECTION_ERROR";
|
|
37
|
+
ORError["TRANSLATION_ERROR"] = "TRANSLATION_ERROR";
|
|
38
|
+
})(ORError || (ORError = {}));
|
|
39
|
+
export var OREvent;
|
|
40
|
+
(function (OREvent) {
|
|
41
|
+
OREvent["ERROR"] = "ERROR";
|
|
42
|
+
OREvent["READY"] = "READY";
|
|
43
|
+
OREvent["ONLINE"] = "ONLINE";
|
|
44
|
+
OREvent["OFFLINE"] = "OFFLINE";
|
|
45
|
+
OREvent["CONNECTING"] = "CONNECTING";
|
|
46
|
+
OREvent["CONSOLE_INIT"] = "CONSOLE_INIT";
|
|
47
|
+
OREvent["CONSOLE_READY"] = "CONSOLE_READY";
|
|
48
|
+
OREvent["TRANSLATE_INIT"] = "TRANSLATE_INIT";
|
|
49
|
+
OREvent["TRANSLATE_LANGUAGE_CHANGED"] = "TRANSLATE_LANGUAGE_CHANGED";
|
|
50
|
+
OREvent["DISPLAY_REALM_CHANGED"] = "DISPLAY_REALM_CHANGED";
|
|
51
|
+
})(OREvent || (OREvent = {}));
|
|
52
|
+
export const DEFAULT_LANGUAGES = {
|
|
53
|
+
en: "english",
|
|
54
|
+
cn: "chinese",
|
|
55
|
+
nl: "dutch",
|
|
56
|
+
fr: "french",
|
|
57
|
+
de: "german",
|
|
58
|
+
it: "italian",
|
|
59
|
+
pt: "portuguese",
|
|
60
|
+
ro: "romanian",
|
|
61
|
+
es: "spanish",
|
|
62
|
+
uk: "ukrainian"
|
|
63
|
+
};
|
|
64
|
+
export function normaliseConfig(config) {
|
|
65
|
+
const normalisedConfig = config ? Object.assign({}, config) : {};
|
|
66
|
+
if (!normalisedConfig.managerUrl || normalisedConfig.managerUrl === "") {
|
|
67
|
+
// Assume manager is running on same host as this code
|
|
68
|
+
normalisedConfig.managerUrl = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "")
|
|
69
|
+
+ window.location.pathname.replace(/\/[^/]+\/?$/, '');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Normalise by stripping any trailing slashes
|
|
73
|
+
normalisedConfig.managerUrl = normalisedConfig.managerUrl.replace(/\/+$/, "");
|
|
74
|
+
}
|
|
75
|
+
if (!normalisedConfig.realm || normalisedConfig.realm === "") {
|
|
76
|
+
// Assume master realm
|
|
77
|
+
normalisedConfig.realm = "master";
|
|
78
|
+
}
|
|
79
|
+
if (!normalisedConfig.auth) {
|
|
80
|
+
normalisedConfig.auth = "KEYCLOAK" /* Auth.KEYCLOAK */;
|
|
81
|
+
}
|
|
82
|
+
if (normalisedConfig.consoleAutoEnable === undefined) {
|
|
83
|
+
normalisedConfig.consoleAutoEnable = true;
|
|
84
|
+
}
|
|
85
|
+
if (normalisedConfig.applyConfigToAdmin === undefined) {
|
|
86
|
+
normalisedConfig.applyConfigToAdmin = true;
|
|
87
|
+
}
|
|
88
|
+
if (!normalisedConfig.eventProviderType) {
|
|
89
|
+
normalisedConfig.eventProviderType = "WEBSOCKET" /* EventProviderType.WEBSOCKET */;
|
|
90
|
+
}
|
|
91
|
+
if (!normalisedConfig.pollingIntervalMillis || normalisedConfig.pollingIntervalMillis < 5000) {
|
|
92
|
+
normalisedConfig.pollingIntervalMillis = 10000;
|
|
93
|
+
}
|
|
94
|
+
if (normalisedConfig.loadIcons === undefined) {
|
|
95
|
+
normalisedConfig.loadIcons = true;
|
|
96
|
+
}
|
|
97
|
+
if (normalisedConfig.loadTranslations === undefined) {
|
|
98
|
+
normalisedConfig.loadTranslations = ["or"];
|
|
99
|
+
}
|
|
100
|
+
if (normalisedConfig.translationsLoadPath === undefined) {
|
|
101
|
+
normalisedConfig.translationsLoadPath = "locales/{{lng}}/{{ns}}.json";
|
|
102
|
+
}
|
|
103
|
+
if (normalisedConfig.loadDescriptors === undefined) {
|
|
104
|
+
normalisedConfig.loadDescriptors = true;
|
|
105
|
+
}
|
|
106
|
+
if (normalisedConfig.clientId === undefined) {
|
|
107
|
+
normalisedConfig.clientId = OPENREMOTE_CLIENT_ID;
|
|
108
|
+
}
|
|
109
|
+
return normalisedConfig;
|
|
110
|
+
}
|
|
111
|
+
export class Manager {
|
|
112
|
+
constructor() {
|
|
113
|
+
this._authenticated = false;
|
|
114
|
+
this._ready = false;
|
|
115
|
+
this._name = "";
|
|
116
|
+
this._username = "";
|
|
117
|
+
this._keycloakUpdateTokenInterval = undefined;
|
|
118
|
+
this._managerVersion = "";
|
|
119
|
+
this._authServerUrl = "";
|
|
120
|
+
this._listeners = [];
|
|
121
|
+
this._disconnected = false;
|
|
122
|
+
}
|
|
123
|
+
get username() {
|
|
124
|
+
return this._username;
|
|
125
|
+
}
|
|
126
|
+
get error() {
|
|
127
|
+
return this._error;
|
|
128
|
+
}
|
|
129
|
+
get authenticated() {
|
|
130
|
+
return this._authenticated;
|
|
131
|
+
}
|
|
132
|
+
get ready() {
|
|
133
|
+
return this._ready;
|
|
134
|
+
}
|
|
135
|
+
get config() {
|
|
136
|
+
return this._config;
|
|
137
|
+
}
|
|
138
|
+
get roles() {
|
|
139
|
+
const roleMap = new Map();
|
|
140
|
+
if (this._keycloak) {
|
|
141
|
+
if (this._keycloak.resourceAccess) {
|
|
142
|
+
if (this._config.clientId && this._keycloak.resourceAccess) {
|
|
143
|
+
Object.entries(this._keycloak.resourceAccess).forEach(([client, resourceObj]) => {
|
|
144
|
+
const roles = resourceObj.roles;
|
|
145
|
+
roleMap.set(client, roles);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (this._basicIdentity && this._basicIdentity.roles) {
|
|
151
|
+
roleMap.set(this._config.clientId, this._basicIdentity.roles);
|
|
152
|
+
}
|
|
153
|
+
return roleMap;
|
|
154
|
+
}
|
|
155
|
+
get managerVersion() {
|
|
156
|
+
return this._managerVersion;
|
|
157
|
+
}
|
|
158
|
+
get isManagerAvailable() {
|
|
159
|
+
return this._managerVersion && this._managerVersion !== "";
|
|
160
|
+
}
|
|
161
|
+
get managerUrl() {
|
|
162
|
+
var _a;
|
|
163
|
+
return (_a = this._config) === null || _a === void 0 ? void 0 : _a.managerUrl;
|
|
164
|
+
}
|
|
165
|
+
get keycloakUrl() {
|
|
166
|
+
var _a;
|
|
167
|
+
return (_a = this._config) === null || _a === void 0 ? void 0 : _a.keycloakUrl;
|
|
168
|
+
}
|
|
169
|
+
get isError() {
|
|
170
|
+
return !!this._error;
|
|
171
|
+
}
|
|
172
|
+
get connectionStatus() {
|
|
173
|
+
return this._events && this._events.status;
|
|
174
|
+
}
|
|
175
|
+
get console() {
|
|
176
|
+
return this._console;
|
|
177
|
+
}
|
|
178
|
+
get consoleAppConfig() {
|
|
179
|
+
return this._consoleAppConfig;
|
|
180
|
+
}
|
|
181
|
+
get events() {
|
|
182
|
+
return this._events;
|
|
183
|
+
}
|
|
184
|
+
get rest() {
|
|
185
|
+
return rest;
|
|
186
|
+
}
|
|
187
|
+
get language() {
|
|
188
|
+
return i18next.language;
|
|
189
|
+
}
|
|
190
|
+
set language(lang) {
|
|
191
|
+
console.debug(`Changing language to ${lang}.`);
|
|
192
|
+
if (lang) {
|
|
193
|
+
i18next.changeLanguage(lang);
|
|
194
|
+
this.console.storeData("LANGUAGE", lang);
|
|
195
|
+
if (this.authenticated) {
|
|
196
|
+
this.updateKeycloakUserLanguage(lang).catch(e => console.error(e));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
get displayRealm() {
|
|
201
|
+
return this._displayRealm || this._config.realm;
|
|
202
|
+
}
|
|
203
|
+
set displayRealm(realm) {
|
|
204
|
+
if (!this.isSuperUser() || this._displayRealm === realm) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this._displayRealm = realm;
|
|
208
|
+
this._emitEvent(OREvent.DISPLAY_REALM_CHANGED);
|
|
209
|
+
}
|
|
210
|
+
get clientId() {
|
|
211
|
+
return this._config.clientId || OPENREMOTE_CLIENT_ID;
|
|
212
|
+
}
|
|
213
|
+
getEventProvider() {
|
|
214
|
+
return this.events;
|
|
215
|
+
}
|
|
216
|
+
get mapType() {
|
|
217
|
+
return this._config.mapType || "VECTOR" /* MapType.VECTOR */;
|
|
218
|
+
}
|
|
219
|
+
isManagerSameOrigin() {
|
|
220
|
+
if (!this.ready) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const managerUrl = new URL(this._config.managerUrl);
|
|
224
|
+
const windowUrl = window.location;
|
|
225
|
+
return managerUrl.protocol === windowUrl.protocol
|
|
226
|
+
&& managerUrl.hostname === windowUrl.hostname
|
|
227
|
+
&& managerUrl.port === windowUrl.port;
|
|
228
|
+
}
|
|
229
|
+
addListener(callback) {
|
|
230
|
+
const index = this._listeners.indexOf(callback);
|
|
231
|
+
if (index < 0) {
|
|
232
|
+
this._listeners.push(callback);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
removeListener(callback) {
|
|
236
|
+
const index = this._listeners.indexOf(callback);
|
|
237
|
+
if (index >= 0) {
|
|
238
|
+
this._listeners.splice(index, 1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
init(config) {
|
|
242
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
243
|
+
if (this._config) {
|
|
244
|
+
console.debug("Already initialised");
|
|
245
|
+
}
|
|
246
|
+
this._config = normaliseConfig(config);
|
|
247
|
+
let success = yield this.loadManagerInfo();
|
|
248
|
+
// Create console as we need to access storage during auth
|
|
249
|
+
const orConsole = new Console(this._config.realm, this._config.consoleAutoEnable, () => {
|
|
250
|
+
this._emitEvent(OREvent.CONSOLE_READY);
|
|
251
|
+
});
|
|
252
|
+
this._console = orConsole;
|
|
253
|
+
if (this._config.auth === "BASIC" /* Auth.BASIC */) {
|
|
254
|
+
// BASIC auth will likely require UI so lets init translation at least
|
|
255
|
+
success = (yield this.doTranslateInit()) && success;
|
|
256
|
+
success = yield this.doAuthInit();
|
|
257
|
+
}
|
|
258
|
+
else if (this._config.auth === "KEYCLOAK" /* Auth.KEYCLOAK */) {
|
|
259
|
+
// The info endpoint of the manager might return a relative URL (relative to the manager)
|
|
260
|
+
if (!this._config.keycloakUrl && this._authServerUrl) {
|
|
261
|
+
const managerURL = new URL(this._config.managerUrl);
|
|
262
|
+
let authServerURL;
|
|
263
|
+
if (this._authServerUrl.startsWith("//")) {
|
|
264
|
+
this._authServerUrl = managerURL.protocol + this._authServerUrl;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
authServerURL = new URL(this._authServerUrl);
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
// Could be a relative URL
|
|
271
|
+
authServerURL = new URL(managerURL);
|
|
272
|
+
authServerURL.pathname = this._authServerUrl;
|
|
273
|
+
}
|
|
274
|
+
// Use manager URL info
|
|
275
|
+
if (!authServerURL.protocol) {
|
|
276
|
+
authServerURL.protocol = managerURL.protocol;
|
|
277
|
+
}
|
|
278
|
+
if (!authServerURL.hostname) {
|
|
279
|
+
authServerURL.hostname = managerURL.hostname;
|
|
280
|
+
}
|
|
281
|
+
if (!authServerURL.port) {
|
|
282
|
+
authServerURL.port = managerURL.port;
|
|
283
|
+
}
|
|
284
|
+
this._config.keycloakUrl = authServerURL.toString();
|
|
285
|
+
}
|
|
286
|
+
// If we still don't know auth server URL then use manager URL
|
|
287
|
+
if (!this._config.keycloakUrl) {
|
|
288
|
+
this._config.keycloakUrl = this._config.managerUrl + "/auth";
|
|
289
|
+
}
|
|
290
|
+
// Normalise by stripping any trailing slashes
|
|
291
|
+
this._config.keycloakUrl = this._config.keycloakUrl.replace(/\/+$/, "");
|
|
292
|
+
success = yield this.doAuthInit();
|
|
293
|
+
// If failed then we can assume keycloak auth requested but unavailable
|
|
294
|
+
if (!success && !this._config.skipFallbackToBasicAuth) {
|
|
295
|
+
// Try fallback to BASIC
|
|
296
|
+
console.debug("Falling back to basic auth");
|
|
297
|
+
this._config.auth = "BASIC" /* Auth.BASIC */;
|
|
298
|
+
success = yield this.doAuthInit();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!success) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
if (success) {
|
|
305
|
+
success = this.doRestApiInit();
|
|
306
|
+
}
|
|
307
|
+
// Don't let console registration error prevent loading
|
|
308
|
+
const consoleSuccess = yield this.doConsoleInit();
|
|
309
|
+
if (consoleSuccess) {
|
|
310
|
+
// Send the console a message to clear the web history, so no pages outside the app can be accessed.
|
|
311
|
+
// For example, this prevents navigating back to an authentication screen.
|
|
312
|
+
this._clearWebHistory();
|
|
313
|
+
}
|
|
314
|
+
success = (yield this.doTranslateInit()) && success;
|
|
315
|
+
if (success) {
|
|
316
|
+
success = yield this.doDescriptorsInit();
|
|
317
|
+
success = yield this.getConsoleAppConfig();
|
|
318
|
+
}
|
|
319
|
+
this.doIconInit();
|
|
320
|
+
// TODO: Reinstate this once websocket supports anonymous connections
|
|
321
|
+
// if (success) {
|
|
322
|
+
// success = await this.doEventsSubscriptionInit();
|
|
323
|
+
// }
|
|
324
|
+
if (success) {
|
|
325
|
+
if (this._readyCallback) {
|
|
326
|
+
yield this._readyCallback();
|
|
327
|
+
}
|
|
328
|
+
this._ready = true;
|
|
329
|
+
this._emitEvent(OREvent.READY);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
this._config = undefined;
|
|
333
|
+
console.warn("Failed to initialise the manager");
|
|
334
|
+
}
|
|
335
|
+
this.displayRealm = config.realm || "master";
|
|
336
|
+
return success;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
loadManagerInfo() {
|
|
340
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
341
|
+
// Check manager exists by calling the info endpoint
|
|
342
|
+
try {
|
|
343
|
+
const json = yield new Promise((resolve, reject) => {
|
|
344
|
+
const oReq = new XMLHttpRequest();
|
|
345
|
+
oReq.addEventListener("load", () => {
|
|
346
|
+
resolve(JSON.parse(oReq.responseText));
|
|
347
|
+
});
|
|
348
|
+
oReq.addEventListener("error", () => {
|
|
349
|
+
reject(new Error("Failed to contact the manager"));
|
|
350
|
+
});
|
|
351
|
+
oReq.open("GET", this._config.managerUrl + "/api/master/info");
|
|
352
|
+
oReq.send();
|
|
353
|
+
});
|
|
354
|
+
this._managerVersion = json && json.version ? json.version : "";
|
|
355
|
+
this._authServerUrl = json && json.authServerUrl ? json.authServerUrl : "";
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
// TODO: Implement auto retry?
|
|
360
|
+
console.error("Failed to contact the manager", e);
|
|
361
|
+
this._setError(ORError.MANAGER_FAILED_TO_LOAD);
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
doTranslateInit() {
|
|
367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
+
if (i18next.isInitialized) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
i18next.on("initialized", (options) => {
|
|
372
|
+
this._emitEvent(OREvent.TRANSLATE_INIT);
|
|
373
|
+
});
|
|
374
|
+
i18next.on("languageChanged", (lng) => {
|
|
375
|
+
moment.locale(lng);
|
|
376
|
+
this._emitEvent(OREvent.TRANSLATE_LANGUAGE_CHANGED);
|
|
377
|
+
});
|
|
378
|
+
// Look for language preference in local storage
|
|
379
|
+
const initOptions = {
|
|
380
|
+
lng: (yield this.getConsolePreferredLanguage()) || (yield this.getUserPreferredLanguage()) || this.config.defaultLanguage || "en",
|
|
381
|
+
fallbackLng: "en",
|
|
382
|
+
defaultNS: "app",
|
|
383
|
+
fallbackNS: "or",
|
|
384
|
+
ns: this.config.loadTranslations,
|
|
385
|
+
interpolation: {
|
|
386
|
+
format: (value, format, lng) => {
|
|
387
|
+
if (format === "uppercase")
|
|
388
|
+
return value.toUpperCase();
|
|
389
|
+
if (value instanceof Date) {
|
|
390
|
+
return moment(value).format(format);
|
|
391
|
+
}
|
|
392
|
+
return value;
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
backend: {
|
|
396
|
+
loadPath: (langs, namespaces) => {
|
|
397
|
+
if (namespaces.length === 1 && namespaces[0] === "or") {
|
|
398
|
+
return this.config.managerUrl + "/shared/locales/{{lng}}/{{ns}}.json";
|
|
399
|
+
}
|
|
400
|
+
if (this.config.translationsLoadPath) {
|
|
401
|
+
return this.config.translationsLoadPath;
|
|
402
|
+
}
|
|
403
|
+
return "locales/{{lng}}/{{ns}}.json";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
if (this.config.configureTranslationsOptions) {
|
|
408
|
+
this.config.configureTranslationsOptions(initOptions);
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
yield i18next.use(i18nextBackend).init(initOptions);
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
console.error(e);
|
|
415
|
+
this._setError(ORError.TRANSLATION_ERROR);
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
doDescriptorsInit() {
|
|
422
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
423
|
+
if (!this.config.loadDescriptors) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
const assetInfosResponse = yield rest.api.AssetModelResource.getAssetInfos();
|
|
428
|
+
const metaItemDescriptorResponse = yield rest.api.AssetModelResource.getMetaItemDescriptors();
|
|
429
|
+
const valueDescriptorResponse = yield rest.api.AssetModelResource.getValueDescriptors();
|
|
430
|
+
AssetModelUtil._assetTypeInfos = assetInfosResponse.data;
|
|
431
|
+
AssetModelUtil._metaItemDescriptors = Object.values(metaItemDescriptorResponse.data);
|
|
432
|
+
AssetModelUtil._valueDescriptors = Object.values(valueDescriptorResponse.data);
|
|
433
|
+
}
|
|
434
|
+
catch (e) {
|
|
435
|
+
console.error(e);
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
doAuthInit() {
|
|
442
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
443
|
+
let success = true;
|
|
444
|
+
switch (this._config.auth) {
|
|
445
|
+
case "BASIC" /* Auth.BASIC */:
|
|
446
|
+
success = yield this.initialiseBasicAuth();
|
|
447
|
+
break;
|
|
448
|
+
case "KEYCLOAK" /* Auth.KEYCLOAK */:
|
|
449
|
+
success = yield this.loadAndInitialiseKeycloak();
|
|
450
|
+
break;
|
|
451
|
+
case "NONE" /* Auth.NONE */:
|
|
452
|
+
// Nothing for us to do here
|
|
453
|
+
return true;
|
|
454
|
+
default:
|
|
455
|
+
this._setError(ORError.AUTH_TYPE_UNSUPPORTED);
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
if (!success) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
// Add interceptor to inject authorization header on each request
|
|
462
|
+
rest.addRequestInterceptor((config) => {
|
|
463
|
+
if (!config.headers.Authorization) {
|
|
464
|
+
const authHeader = this.getAuthorizationHeader();
|
|
465
|
+
if (authHeader) {
|
|
466
|
+
config.headers.Authorization = authHeader;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return config;
|
|
470
|
+
});
|
|
471
|
+
return success;
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
doRestApiInit() {
|
|
475
|
+
rest.setTimeout(20000);
|
|
476
|
+
rest.initialise(this.getApiBaseUrl());
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
doEventsSubscriptionInit() {
|
|
480
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
481
|
+
let connected = false;
|
|
482
|
+
switch (this._config.eventProviderType) {
|
|
483
|
+
case "WEBSOCKET" /* EventProviderType.WEBSOCKET */:
|
|
484
|
+
this._events = new WebSocketEventProvider(this._config.managerUrl);
|
|
485
|
+
this._events.subscribeStatusChange((status) => this._onEventProviderStatusChanged(status));
|
|
486
|
+
connected = yield this._events.connect();
|
|
487
|
+
break;
|
|
488
|
+
case "POLLING" /* EventProviderType.POLLING */:
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
if (!connected) {
|
|
492
|
+
this._setError(ORError.EVENTS_CONNECTION_ERROR);
|
|
493
|
+
}
|
|
494
|
+
return connected;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
// Function that connects the EventProvider.
|
|
498
|
+
_connectEvents() {
|
|
499
|
+
var _a;
|
|
500
|
+
if (((_a = this.events) === null || _a === void 0 ? void 0 : _a.status) === EventProviderStatus.DISCONNECTED) {
|
|
501
|
+
this.events.connect().catch((e) => {
|
|
502
|
+
console.error(`Failed to connect EventProvider.`);
|
|
503
|
+
console.error(e);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
console.warn("Tried to connect EventProvider, but it wasn't disconnected!");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
_onEventProviderStatusChanged(status) {
|
|
511
|
+
switch (status) {
|
|
512
|
+
case EventProviderStatus.DISCONNECTED:
|
|
513
|
+
this._onDisconnect();
|
|
514
|
+
break;
|
|
515
|
+
case EventProviderStatus.CONNECTED:
|
|
516
|
+
break;
|
|
517
|
+
case EventProviderStatus.CONNECTING:
|
|
518
|
+
this._emitEvent(OREvent.CONNECTING);
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
doConsoleInit() {
|
|
523
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
524
|
+
try {
|
|
525
|
+
yield this.console.initialise();
|
|
526
|
+
this._emitEvent(OREvent.CONSOLE_INIT);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
this._setError(ORError.CONSOLE_ERROR);
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
doIconInit() {
|
|
536
|
+
// Load material design and OR icon sets if requested
|
|
537
|
+
if (this._config.loadIcons) {
|
|
538
|
+
IconSets.addIconSet("mdi", createMdiIconSet(manager.managerUrl));
|
|
539
|
+
IconSets.addIconSet("or", createSvgIconSet(OrIconSet.size, OrIconSet.icons));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
getConsoleAppConfig() {
|
|
543
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
544
|
+
var _a;
|
|
545
|
+
try {
|
|
546
|
+
const response = yield fetch(((_a = manager.managerUrl) !== null && _a !== void 0 ? _a : "") + "/consoleappconfig/" + manager.displayRealm + ".json");
|
|
547
|
+
this._consoleAppConfig = (yield response.json());
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
catch (e) {
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Checks the native console to gather the preferred language of the device.
|
|
557
|
+
*/
|
|
558
|
+
getConsolePreferredLanguage() {
|
|
559
|
+
return __awaiter(this, arguments, void 0, function* (orConsole = this.console) {
|
|
560
|
+
return orConsole.retrieveData("LANGUAGE");
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Checks the keycloak access token to gather the preferred language of a user.
|
|
565
|
+
*/
|
|
566
|
+
getUserPreferredLanguage() {
|
|
567
|
+
return __awaiter(this, arguments, void 0, function* (keycloak = this._keycloak) {
|
|
568
|
+
if (keycloak && keycloak.authenticated) {
|
|
569
|
+
const profile = (keycloak === null || keycloak === void 0 ? void 0 : keycloak.profile) || (yield (keycloak === null || keycloak === void 0 ? void 0 : keycloak.loadUserProfile()));
|
|
570
|
+
if (profile === null || profile === void 0 ? void 0 : profile.attributes) {
|
|
571
|
+
const attributes = new Map(Object.entries(profile.attributes));
|
|
572
|
+
if (attributes.has("locale")) {
|
|
573
|
+
const attr = attributes.get("locale");
|
|
574
|
+
if (typeof attr[0] === "string") {
|
|
575
|
+
return attr[0];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
console.warn("Could not get user language from keycloak: no user attributes were found.");
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
console.warn("Could not get user language from keycloak: no valid keycloak user profile was found.");
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
updateKeycloakUserLanguage(lang_1) {
|
|
587
|
+
return __awaiter(this, arguments, void 0, function* (lang, rest = this.rest) {
|
|
588
|
+
if (!this.authenticated) {
|
|
589
|
+
console.warn("Tried updating user language, but the user is not authenticated.");
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (!rest) {
|
|
593
|
+
console.warn("Tried updating user language, but the REST API is not initialized yet.");
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
yield rest.api.UserResource.updateCurrentUserLocale(lang, { headers: { "Content-Type": "application/json" } });
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
logout(redirectUrl) {
|
|
600
|
+
if (!this._authenticated) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
this._authenticated = true;
|
|
604
|
+
if (this._keycloak) {
|
|
605
|
+
if (this.isMobile()) {
|
|
606
|
+
this.console.storeData("REFRESH_TOKEN", null);
|
|
607
|
+
}
|
|
608
|
+
if (this._keycloakUpdateTokenInterval) {
|
|
609
|
+
window.clearTimeout(this._keycloakUpdateTokenInterval);
|
|
610
|
+
this._keycloakUpdateTokenInterval = undefined;
|
|
611
|
+
}
|
|
612
|
+
this._keycloak.logout(redirectUrl && redirectUrl !== "" ? { redirectUri: redirectUrl } : undefined);
|
|
613
|
+
}
|
|
614
|
+
else if (this._basicIdentity) {
|
|
615
|
+
this._basicIdentity = undefined;
|
|
616
|
+
if (redirectUrl) {
|
|
617
|
+
window.location.href = redirectUrl;
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
window.location.reload();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
login(options) {
|
|
625
|
+
switch (this._config.auth) {
|
|
626
|
+
case "BASIC" /* Auth.BASIC */:
|
|
627
|
+
if (options && options.credentials) {
|
|
628
|
+
this._config.credentials = Object.assign({}, options.credentials);
|
|
629
|
+
}
|
|
630
|
+
this.doBasicLogin();
|
|
631
|
+
break;
|
|
632
|
+
case "KEYCLOAK" /* Auth.KEYCLOAK */:
|
|
633
|
+
if (this._keycloak) {
|
|
634
|
+
const keycloakOptions = {};
|
|
635
|
+
if (options && options.redirectUrl && options.redirectUrl !== "") {
|
|
636
|
+
keycloakOptions.redirectUri = options.redirectUrl;
|
|
637
|
+
}
|
|
638
|
+
if ((options === null || options === void 0 ? void 0 : options.action) && options.action !== "") {
|
|
639
|
+
keycloakOptions.action = options.action;
|
|
640
|
+
}
|
|
641
|
+
if (this.isMobile()) {
|
|
642
|
+
keycloakOptions.scope = "offline_access";
|
|
643
|
+
}
|
|
644
|
+
this._keycloak.login(keycloakOptions);
|
|
645
|
+
}
|
|
646
|
+
break;
|
|
647
|
+
case "NONE" /* Auth.NONE */:
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
initialiseBasicAuth() {
|
|
652
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
653
|
+
if (!this.config.basicLoginProvider) {
|
|
654
|
+
console.debug("No basicLoginProvider defined on config so cannot display login UI");
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
if (this.config.autoLogin) {
|
|
658
|
+
// Delay basic login until other inits are done
|
|
659
|
+
this._readyCallback = () => {
|
|
660
|
+
return this.doBasicLogin();
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
return true;
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
doBasicLogin() {
|
|
667
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
668
|
+
var _a, _b, _c, _d;
|
|
669
|
+
if (!this.config.basicLoginProvider) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
let result = {
|
|
673
|
+
username: ((_a = this.config.credentials) === null || _a === void 0 ? void 0 : _a.username) ? (_b = this.config.credentials) === null || _b === void 0 ? void 0 : _b.username : "",
|
|
674
|
+
password: ((_c = this.config.credentials) === null || _c === void 0 ? void 0 : _c.password) ? (_d = this.config.credentials) === null || _d === void 0 ? void 0 : _d.password : "",
|
|
675
|
+
cancel: false
|
|
676
|
+
};
|
|
677
|
+
let authenticated = false;
|
|
678
|
+
this._basicIdentity = {
|
|
679
|
+
roles: undefined,
|
|
680
|
+
token: undefined,
|
|
681
|
+
user: undefined
|
|
682
|
+
};
|
|
683
|
+
while (!authenticated) {
|
|
684
|
+
result = yield this.config.basicLoginProvider(result.username, result.password);
|
|
685
|
+
if (result.cancel) {
|
|
686
|
+
console.debug("Basic authentication cancelled by user");
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
if (!result.username || !result.password) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
// Update basic token so we can use rest api to make calls
|
|
693
|
+
this._basicIdentity.token = btoa(result.username + ":" + result.password);
|
|
694
|
+
let success = false;
|
|
695
|
+
try {
|
|
696
|
+
const userResponse = yield rest.api.UserResource.getCurrent();
|
|
697
|
+
if (userResponse.status === 200) {
|
|
698
|
+
success = true;
|
|
699
|
+
this._basicIdentity.user = userResponse.data;
|
|
700
|
+
}
|
|
701
|
+
if (!success) {
|
|
702
|
+
// Undertow incorrectly returns 403 when no authorization header and a 401 when it is set and not valid
|
|
703
|
+
if (userResponse.status === 401 || userResponse.status === 403) {
|
|
704
|
+
console.debug("Basic authentication invalid credentials, trying again");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
console.error("Basic auth failed: ", e);
|
|
710
|
+
}
|
|
711
|
+
if (success) {
|
|
712
|
+
console.debug("Basic authentication successful");
|
|
713
|
+
authenticated = true;
|
|
714
|
+
// Get user roles
|
|
715
|
+
const rolesResponse = yield rest.api.UserResource.getCurrentUserClientRoles(this.clientId);
|
|
716
|
+
this._basicIdentity.roles = rolesResponse.data;
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
console.debug("Unknown response so aborting");
|
|
720
|
+
this._basicIdentity = undefined;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (authenticated) {
|
|
725
|
+
this._onAuthenticated();
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
isSuperUser() {
|
|
730
|
+
return !!(this.getRealm() && this.getRealm() === "master" && this.hasRealmRole("admin"));
|
|
731
|
+
}
|
|
732
|
+
isRestrictedUser() {
|
|
733
|
+
return !!this.hasRealmRole(RESTRICTED_USER_REALM_ROLE);
|
|
734
|
+
}
|
|
735
|
+
getApiBaseUrl() {
|
|
736
|
+
let baseUrl = this._config.managerUrl;
|
|
737
|
+
baseUrl += "/api/" + this._config.realm + "/";
|
|
738
|
+
return baseUrl;
|
|
739
|
+
}
|
|
740
|
+
getAppName() {
|
|
741
|
+
const pathArr = location.pathname.split('/');
|
|
742
|
+
return pathArr.length >= 1 ? pathArr[1] : "";
|
|
743
|
+
}
|
|
744
|
+
hasRealmRole(role) {
|
|
745
|
+
return this.isKeycloak() && this._keycloak.hasRealmRole(role);
|
|
746
|
+
}
|
|
747
|
+
hasRole(role, client = this._config.clientId) {
|
|
748
|
+
const roles = this.roles;
|
|
749
|
+
return roles && roles.has(client) && roles.get(client).indexOf(role) >= 0;
|
|
750
|
+
}
|
|
751
|
+
getAuthorizationHeader() {
|
|
752
|
+
if (this.getKeycloakToken()) {
|
|
753
|
+
return "Bearer " + this.getKeycloakToken();
|
|
754
|
+
}
|
|
755
|
+
if (this.getBasicToken()) {
|
|
756
|
+
return "Basic " + this.getBasicToken();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
getKeycloakToken() {
|
|
760
|
+
if (this.isKeycloak()) {
|
|
761
|
+
return this._keycloak.token;
|
|
762
|
+
}
|
|
763
|
+
return undefined;
|
|
764
|
+
}
|
|
765
|
+
getBasicToken() {
|
|
766
|
+
return this._basicIdentity ? this._basicIdentity.token : undefined;
|
|
767
|
+
}
|
|
768
|
+
getRealm() {
|
|
769
|
+
if (this._config) {
|
|
770
|
+
return this._config.realm;
|
|
771
|
+
}
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
isMobile() {
|
|
775
|
+
return this.console && this.console.isMobile;
|
|
776
|
+
}
|
|
777
|
+
isKeycloak() {
|
|
778
|
+
return !!this._keycloak;
|
|
779
|
+
}
|
|
780
|
+
_onAuthenticated() {
|
|
781
|
+
this._authenticated = true;
|
|
782
|
+
// TODO: Move events init logic once websocket supports anonymous connections
|
|
783
|
+
if (!this._events) {
|
|
784
|
+
this.doEventsSubscriptionInit();
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
loadAndInitialiseKeycloak() {
|
|
788
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
789
|
+
var _a, _b, _c, _d;
|
|
790
|
+
try {
|
|
791
|
+
// Initialise keycloak
|
|
792
|
+
this._keycloak = new Keycloak({
|
|
793
|
+
clientId: this._config.clientId,
|
|
794
|
+
realm: this._config.realm,
|
|
795
|
+
url: this._config.keycloakUrl
|
|
796
|
+
});
|
|
797
|
+
// Try to use a stored offline refresh token if defined
|
|
798
|
+
const offlineToken = yield this._getNativeOfflineRefreshToken();
|
|
799
|
+
// Cannot inject offlineToken here as this adapter is designed for interactive login and even check-sso
|
|
800
|
+
// does a redirect to keycloak but we can inject the offline token as shown and then update the access token
|
|
801
|
+
let authenticated = yield this._keycloak.init({
|
|
802
|
+
checkLoginIframe: false, // Doesn't work well with offline tokens or periodic token updates
|
|
803
|
+
onLoad: "check-sso"
|
|
804
|
+
});
|
|
805
|
+
if (!authenticated && offlineToken) {
|
|
806
|
+
try {
|
|
807
|
+
console.error("SETTING OFFLINE TOKEN");
|
|
808
|
+
this._keycloak.refreshToken = offlineToken;
|
|
809
|
+
authenticated = yield this._updateKeycloakAccessToken();
|
|
810
|
+
}
|
|
811
|
+
catch (e) {
|
|
812
|
+
console.error("Failed to authenticate using offline token");
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (authenticated) {
|
|
816
|
+
this._name = (_a = this._keycloak.tokenParsed) === null || _a === void 0 ? void 0 : _a.name;
|
|
817
|
+
this._username = (_b = this._keycloak.tokenParsed) === null || _b === void 0 ? void 0 : _b.preferred_username;
|
|
818
|
+
this._createTokenUpdateInterval();
|
|
819
|
+
// If native shell is enabled store offline token
|
|
820
|
+
if (this.isMobile() && ((_d = (_c = this._keycloak) === null || _c === void 0 ? void 0 : _c.refreshTokenParsed) === null || _d === void 0 ? void 0 : _d.typ) === "Offline") {
|
|
821
|
+
console.debug("Storing offline refresh token");
|
|
822
|
+
this.console.storeData("REFRESH_TOKEN", this._keycloak.refreshToken);
|
|
823
|
+
}
|
|
824
|
+
this._onAuthenticated();
|
|
825
|
+
}
|
|
826
|
+
else if (this.config.autoLogin) {
|
|
827
|
+
this.login();
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
this._authenticated = false;
|
|
834
|
+
this._setError(ORError.AUTH_FAILED);
|
|
835
|
+
console.error("Failed to initialise Keycloak: " + error);
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
_createTokenUpdateInterval() {
|
|
841
|
+
if (!this._keycloakUpdateTokenInterval) {
|
|
842
|
+
this._keycloakUpdateTokenInterval = window.setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
843
|
+
yield this._updateKeycloakAccessToken().catch(() => {
|
|
844
|
+
console.debug("Keycloak failed to refresh the access token");
|
|
845
|
+
this._onDisconnect();
|
|
846
|
+
});
|
|
847
|
+
}), 10000);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
_updateKeycloakAccessToken() {
|
|
851
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
852
|
+
const tokenRefreshed = yield this._keycloak.updateToken(20);
|
|
853
|
+
console.debug("Access token update success, refreshed from server: " + tokenRefreshed);
|
|
854
|
+
if (tokenRefreshed) {
|
|
855
|
+
this._onAuthenticated();
|
|
856
|
+
}
|
|
857
|
+
return tokenRefreshed;
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
_getNativeOfflineRefreshToken() {
|
|
861
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
862
|
+
if (this.isMobile()) {
|
|
863
|
+
return yield this.console.retrieveData("REFRESH_TOKEN");
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
_emitEvent(event) {
|
|
868
|
+
window.setTimeout(() => {
|
|
869
|
+
const listeners = this._listeners;
|
|
870
|
+
for (const listener of listeners) {
|
|
871
|
+
listener(event);
|
|
872
|
+
}
|
|
873
|
+
}, 0);
|
|
874
|
+
}
|
|
875
|
+
_setError(error) {
|
|
876
|
+
this._error = error;
|
|
877
|
+
this._emitEvent(OREvent.ERROR);
|
|
878
|
+
console.warn("Error set: " + error);
|
|
879
|
+
}
|
|
880
|
+
/** Function that clears the `WebView` history of a console. It will not delete the history on regular browsers. */
|
|
881
|
+
_clearWebHistory() {
|
|
882
|
+
var _a;
|
|
883
|
+
(_a = this.console) === null || _a === void 0 ? void 0 : _a._doSendGenericMessage("CLEAR_WEB_HISTORY", undefined);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Disconnect can occur when events status changes to offline and/or keycloak token refresh fails
|
|
887
|
+
* we just try to reach keycloak and then get a new token as well as wait for the events status to change
|
|
888
|
+
*/
|
|
889
|
+
_onDisconnect() {
|
|
890
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
891
|
+
if (this._disconnected) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
console.debug("Disconnected");
|
|
895
|
+
this._disconnected = true;
|
|
896
|
+
// Cancel token refresh timer
|
|
897
|
+
if (this._keycloakUpdateTokenInterval) {
|
|
898
|
+
window.clearTimeout(this._keycloakUpdateTokenInterval);
|
|
899
|
+
this._keycloakUpdateTokenInterval = undefined;
|
|
900
|
+
}
|
|
901
|
+
this._emitEvent(OREvent.OFFLINE);
|
|
902
|
+
this.reconnect();
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
_onReconnected() {
|
|
906
|
+
console.debug("Reconnected");
|
|
907
|
+
this._disconnected = false;
|
|
908
|
+
// Reinstate token update interval
|
|
909
|
+
this._createTokenUpdateInterval();
|
|
910
|
+
this._emitEvent(OREvent.ONLINE);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Checks keycloak is available and token is valid otherwise will redirect to login; also checks if event bus is
|
|
914
|
+
* online.
|
|
915
|
+
*/
|
|
916
|
+
reconnect() {
|
|
917
|
+
return __awaiter(this, arguments, void 0, function* (reattemptDelayMillis = 3000) {
|
|
918
|
+
if (!this._disconnected) {
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (this._reconnectTimer) {
|
|
922
|
+
window.clearTimeout(this._reconnectTimer);
|
|
923
|
+
this._reconnectTimer = undefined;
|
|
924
|
+
}
|
|
925
|
+
const tryReconnect = () => __awaiter(this, void 0, void 0, function* () {
|
|
926
|
+
var _a;
|
|
927
|
+
console.debug("Attempting reconnect");
|
|
928
|
+
let keycloakOffline = !(yield this.isKeycloakReachable());
|
|
929
|
+
if (keycloakOffline) {
|
|
930
|
+
console.debug("Keycloak is unreachable");
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
console.debug("Keycloak is reachable");
|
|
934
|
+
// Check if access token can be refreshed
|
|
935
|
+
console.debug("Checking keycloak access token");
|
|
936
|
+
try {
|
|
937
|
+
yield this._updateKeycloakAccessToken();
|
|
938
|
+
}
|
|
939
|
+
catch (e) {
|
|
940
|
+
// Try and use offline token if it is available
|
|
941
|
+
const offlineToken = yield this._getNativeOfflineRefreshToken();
|
|
942
|
+
this._keycloak.refreshToken = offlineToken;
|
|
943
|
+
try {
|
|
944
|
+
yield this._updateKeycloakAccessToken();
|
|
945
|
+
}
|
|
946
|
+
catch (e) {
|
|
947
|
+
console.debug("Cannot update access token so sending to login");
|
|
948
|
+
this.login();
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
console.debug("Keycloak access token is valid");
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
// Check events
|
|
955
|
+
const eventsOffline = this.events && this.events.status === EventProviderStatus.CONNECTING;
|
|
956
|
+
console.debug("If event provider offline then attempting reconnect: offline=" + eventsOffline);
|
|
957
|
+
// Force reconnect attempt now if needed
|
|
958
|
+
return !eventsOffline || (yield ((_a = this.events) === null || _a === void 0 ? void 0 : _a.connect()));
|
|
959
|
+
});
|
|
960
|
+
const connected = yield tryReconnect();
|
|
961
|
+
if (connected === undefined) {
|
|
962
|
+
// Going back to keycloak login so nothing to do
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (!connected) {
|
|
966
|
+
// Schedule reconnect again
|
|
967
|
+
reattemptDelayMillis = Math.min(Manager.MAX_RECONNECT_DELAY, reattemptDelayMillis + 3000);
|
|
968
|
+
console.debug("Scheduling another reconnect attempt in (ms): " + reattemptDelayMillis);
|
|
969
|
+
this._reconnectTimer = window.setTimeout(() => this.reconnect(reattemptDelayMillis), reattemptDelayMillis);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
this._onReconnected();
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
// Checks whether keycloak is reachable using a simple HTTP HEAD request since the keycloak JS adapter doesn't give
|
|
976
|
+
// us details of the HTTP responses they get, we test it manually using this.
|
|
977
|
+
isKeycloakReachable() {
|
|
978
|
+
return __awaiter(this, arguments, void 0, function* (timeoutMillis = 2000) {
|
|
979
|
+
const controller = new AbortController();
|
|
980
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMillis);
|
|
981
|
+
try {
|
|
982
|
+
// Get the token URL from keycloak as this must already have CORS configured for keycloak to work
|
|
983
|
+
// Make an OPTIONS request as GET/HEAD requests are not allowed on the token endpoint
|
|
984
|
+
// Note if keycloak service is unavailable the proxy may respond with 503 and no CORS headers
|
|
985
|
+
// but this is handled by the catch block
|
|
986
|
+
// @ts-ignore
|
|
987
|
+
const tokenUrl = this._keycloak.endpoints.token();
|
|
988
|
+
const result = yield fetch(tokenUrl, { method: 'OPTIONS', signal: controller.signal });
|
|
989
|
+
return result.status === 200;
|
|
990
|
+
}
|
|
991
|
+
catch (e) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
finally {
|
|
995
|
+
clearTimeout(timeout);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
Manager.MAX_RECONNECT_DELAY = 45000;
|
|
1001
|
+
export const manager = new Manager(); // Needed for webpack bundling
|
|
1002
|
+
export default manager;
|
|
1003
|
+
//# sourceMappingURL=index.js.map
|