oidc-client-rx
Version:
ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications
155 lines (154 loc) • 8.86 kB
JavaScript
import { Injectable, inject } from "injection-js";
import { BehaviorSubject, Observable, of } from "rxjs";
import { take } from "rxjs/operators";
import { DOCUMENT } from "../dom/index.js";
import { LoggerService } from "../logging/logger.service.js";
import { EventTypes } from "../public-events/event-types.js";
import { PublicEventsService } from "../public-events/public-events.service.js";
import { DESTORY_REF } from "../resources/index.js";
import { StoragePersistenceService } from "../storage/storage-persistence.service.js";
import { IFrameService } from "./existing-iframe.service.js";
function _ts_decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : null === desc ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if ("object" == typeof Reflect && "function" == typeof Reflect.decorate) r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata(k, v) {
if ("object" == typeof Reflect && "function" == typeof Reflect.metadata) return Reflect.metadata(k, v);
}
const IFRAME_FOR_CHECK_SESSION_IDENTIFIER = "myiFrameForCheckSession";
class CheckSessionService {
get checkSessionChanged$() {
return this.checkSessionChangedInternal$.asObservable();
}
isCheckSessionConfigured(configuration) {
const { startCheckSession } = configuration;
return Boolean(startCheckSession);
}
start(configuration) {
if (this.scheduledHeartBeatRunning) return;
const { clientId } = configuration;
this.pollServerSession(clientId, configuration);
}
stop() {
if (!this.scheduledHeartBeatRunning) return;
this.clearScheduledHeartBeat();
this.checkSessionReceived = false;
}
serverStateChanged(configuration) {
const { startCheckSession } = configuration;
return Boolean(startCheckSession) && this.checkSessionReceived;
}
getExistingIframe() {
return this.iFrameService.getExistingIFrame(IFRAME_FOR_CHECK_SESSION_IDENTIFIER);
}
init(configuration) {
if (this.lastIFrameRefresh + this.iframeRefreshInterval > Date.now()) return of(void 0);
const authWellKnownEndPoints = this.storagePersistenceService.read("authWellKnownEndPoints", configuration);
if (!authWellKnownEndPoints) {
this.loggerService.logWarning(configuration, "CheckSession - init check session: authWellKnownEndpoints is undefined. Returning.");
return of(void 0);
}
const existingIframe = this.getOrCreateIframe(configuration);
this.bindMessageEventToIframe(configuration);
const checkSessionIframe = authWellKnownEndPoints.checkSessionIframe;
const contentWindow = existingIframe.contentWindow;
if (!checkSessionIframe) {
this.loggerService.logWarning(configuration, "CheckSession - init check session: checkSessionIframe is not configured to run");
return of(void 0);
}
if (contentWindow) contentWindow.location.replace(checkSessionIframe);
else this.loggerService.logWarning(configuration, "CheckSession - init check session: IFrame contentWindow does not exist");
return new Observable((observer)=>{
existingIframe.onload = ()=>{
this.lastIFrameRefresh = Date.now();
observer.next(void 0);
observer.complete();
};
});
}
pollServerSession(clientId, configuration) {
this.outstandingMessages = 0;
const pollServerSessionRecur = ()=>{
this.init(configuration).pipe(take(1)).subscribe(()=>{
const existingIframe = this.getExistingIframe();
if (existingIframe && clientId) {
this.loggerService.logDebug(configuration, `CheckSession - clientId : '${clientId}' - existingIframe: '${existingIframe}'`);
const sessionState = this.storagePersistenceService.read("session_state", configuration);
const authWellKnownEndPoints = this.storagePersistenceService.read("authWellKnownEndPoints", configuration);
const contentWindow = existingIframe.contentWindow;
if (sessionState && authWellKnownEndPoints?.checkSessionIframe && contentWindow) {
const iframeOrigin = new URL(authWellKnownEndPoints.checkSessionIframe)?.origin;
this.outstandingMessages++;
contentWindow.postMessage(`${clientId} ${sessionState}`, iframeOrigin);
} else {
this.loggerService.logDebug(configuration, `CheckSession - session_state is '${sessionState}' - AuthWellKnownEndPoints is '${JSON.stringify(authWellKnownEndPoints, null, 2)}'`);
this.checkSessionChangedInternal$.next(true);
}
} else this.loggerService.logWarning(configuration, `CheckSession - OidcSecurityCheckSession pollServerSession checkSession IFrame does not exist:
clientId : '${clientId}' - existingIframe: '${existingIframe}'`);
if (this.outstandingMessages > 3) this.loggerService.logError(configuration, `CheckSession - OidcSecurityCheckSession not receiving check session response messages.
Outstanding messages: '${this.outstandingMessages}'. Server unreachable?`);
this.scheduledHeartBeatRunning = this.document?.defaultView?.setTimeout(pollServerSessionRecur, this.heartBeatInterval) ?? null;
});
};
pollServerSessionRecur();
}
clearScheduledHeartBeat() {
if (null !== this.scheduledHeartBeatRunning) {
clearTimeout(this.scheduledHeartBeatRunning);
this.scheduledHeartBeatRunning = null;
}
}
messageHandler(configuration, e) {
const existingIFrame = this.getExistingIframe();
const authWellKnownEndPoints = this.storagePersistenceService.read("authWellKnownEndPoints", configuration);
const startsWith = !!authWellKnownEndPoints?.checkSessionIframe?.startsWith(e.origin);
this.outstandingMessages = 0;
if (existingIFrame && startsWith && e.source === existingIFrame.contentWindow) if ("error" === e.data) this.loggerService.logWarning(configuration, "CheckSession - error from check session messageHandler");
else if ("changed" === e.data) {
this.loggerService.logDebug(configuration, `CheckSession - ${e} from check session messageHandler`);
this.checkSessionReceived = true;
this.eventService.fireEvent(EventTypes.CheckSessionReceived, e.data);
this.checkSessionChangedInternal$.next(true);
} else {
this.eventService.fireEvent(EventTypes.CheckSessionReceived, e.data);
this.loggerService.logDebug(configuration, `CheckSession - ${e.data} from check session messageHandler`);
}
}
bindMessageEventToIframe(configuration) {
this.iframeMessageEventListener = this.messageHandler.bind(this, configuration);
const defaultView = this.document.defaultView;
if (defaultView) defaultView.addEventListener("message", this.iframeMessageEventListener, false);
}
getOrCreateIframe(configuration) {
return this.getExistingIframe() || this.iFrameService.addIFrameToWindowBody(IFRAME_FOR_CHECK_SESSION_IDENTIFIER, configuration);
}
constructor(){
this.destoryRef$ = inject(DESTORY_REF);
this.loggerService = inject(LoggerService);
this.storagePersistenceService = inject(StoragePersistenceService);
this.iFrameService = inject(IFrameService);
this.eventService = inject(PublicEventsService);
this.document = inject(DOCUMENT);
this.checkSessionReceived = false;
this.scheduledHeartBeatRunning = null;
this.lastIFrameRefresh = 0;
this.outstandingMessages = 0;
this.heartBeatInterval = 3000;
this.iframeRefreshInterval = 60000;
this.checkSessionChangedInternal$ = new BehaviorSubject(false);
this.destoryRef$.subscribe(()=>{
this.stop();
const windowAsDefaultView = this.document.defaultView;
if (windowAsDefaultView && this.iframeMessageEventListener) windowAsDefaultView.removeEventListener("message", this.iframeMessageEventListener, false);
});
}
}
CheckSessionService = _ts_decorate([
Injectable(),
_ts_metadata("design:type", Function),
_ts_metadata("design:paramtypes", [])
], CheckSessionService);
export { CheckSessionService };