UNPKG

@openpass/openpass-js-sdk

Version:
293 lines 13.9 kB
import { getOpenPassApiBaseUrl } from "../config"; import { SdkError } from "./error/errors"; import { QUICK_AUTH_MESSAGE_TYPE, SILENT_AUTH_OUTGOING_MESSAGE_TYPE } from "./constants"; import { matchesEventOrigin } from "./url"; import { addAnalyticsDimension, reportAnalyticsEvent } from "./utils/userAnalytics"; const DELAY_MS_DEFAULT = 1000; export const QUICK_AUTH_LOCAL_STORAGE_KEY = "openPassQuickAuthDismissalData"; /** * Handles rendering and functionality of quick-auth dialog. */ export default class QuickAuth { /** * Instantiates a new instance of the QuickAuth class. * @param openPassOptions - The OpenPass options. * @param redirectAuth - The redirect authentication object. * @param popupAuth - The popup authentication object. * @param apiClient - Holds all methods that call the OpenPass API. * @param silentAuth - The silent authentication object. */ constructor(openPassOptions, redirectAuth, popupAuth, apiClient, silentAuth) { this.quickAuthSignInOptions = null; this.isInitialized = false; this.showInstantly = (ignoreVisibilityCheck = false) => { var _a; if (!this.isInitialized) throw new SdkError("Quick Auth is not initialized. Call `render` first."); if (!ignoreVisibilityCheck && this.isVisible === true) return; this.isVisible = true; this.quickAuthDialogIFrame.style.display = "block"; if ((_a = this.quickAuthSignInOptions) === null || _a === void 0 ? void 0 : _a.visibilityChangedCallback) { reportAnalyticsEvent("quick-auth-visibility-updated", { visibility: "visible" }); this.quickAuthSignInOptions.visibilityChangedCallback({ visibility: "visible" }); } this.apiClient.sendClientTelemetryEvent("SignInWithOpenPassQuickSignShown"); }; this.hideInstantly = () => { var _a; if (!this.isInitialized) throw new SdkError("Quick Auth is not initialized. Call `render` first."); if (this.isVisible === false) return; this.isVisible = false; this.quickAuthDialogIFrame.style.display = "none"; if ((_a = this.quickAuthSignInOptions) === null || _a === void 0 ? void 0 : _a.visibilityChangedCallback) { reportAnalyticsEvent("quick-auth-visibility-updated", { visibility: "hidden" }); this.quickAuthSignInOptions.visibilityChangedCallback({ visibility: "hidden" }); } }; this.openPassOptions = openPassOptions; this.popupAuth = popupAuth; this.redirectAuth = redirectAuth; this.apiClient = apiClient; this.silentAuth = silentAuth; } /** * Renders the quick-auth dialog with the specified options. * @param options - The quick-auth dialog options. * @throws {SdkError} If the required options are missing. */ render(options) { this.quickAuthSignInOptions = options; const { redirectUrl, show, visibility, parentContainerElementId, authenticationMode, popupSuccessCallback } = options; if (visibility === undefined) { // default visibility to "displayOnInit" unless `show` has been explicitly set to false. this.quickAuthSignInOptions.visibility = show === undefined || show === true ? "displayOnInit" : "hideOnInit"; } if (!parentContainerElementId) { throw new SdkError("parentContainerElementId is required for quick-auth sign-in method."); } const temporaryParentContainer = document.getElementById(parentContainerElementId); if (!temporaryParentContainer) throw new SdkError(`Cannot locate parent container element "${parentContainerElementId}" for quick-auth`); this.parentContainer = temporaryParentContainer; if (authenticationMode !== "popup" && authenticationMode !== "redirect") { throw new SdkError(`Invalid authentication mode: ${authenticationMode}.`); } if (authenticationMode === "popup" && !popupSuccessCallback) { throw new SdkError("Must provide popupSuccessCallback for quick-auth when authentication mode is popup."); } if (authenticationMode === "redirect" && !redirectUrl) { throw new SdkError("Must provide redirectUrl for quick-auth when authentication mode is redirect."); } this.quickAuthDialogIFrame = this.createHiddenQuickAuthIframe(this.openPassOptions.clientId, this.parentContainer); window.addEventListener("message", (event) => this.messageHandler(event, options, this.quickAuthDialogIFrame)); this.parentContainer.appendChild(this.quickAuthDialogIFrame); // Report analytics event reportAnalyticsEvent("quick-auth-rendered"); } showWithDelay() { var _a, _b; if (!this.isInitialized) throw new SdkError("Quick Auth is not initialized. Call `render` first."); if (this.isVisible === true) return; // Set this now, so that this method can't be executed again before the timeout. this.isVisible = true; const timeout = setTimeout(() => { this.showInstantly(true); clearTimeout(timeout); }, (_b = (_a = this.quickAuthSignInOptions) === null || _a === void 0 ? void 0 : _a.delayMs) !== null && _b !== void 0 ? _b : DELAY_MS_DEFAULT); } messageHandler(event, options, quickAuthDialogIFrame) { if (!matchesEventOrigin(event.origin, getOpenPassApiBaseUrl(this.openPassOptions.baseUrl))) return; if (event.data && event.data.type) { switch (event.data.type) { case QUICK_AUTH_MESSAGE_TYPE.Init: this.handleInitializedMessage(event, options); break; case QUICK_AUTH_MESSAGE_TYPE.Continue: this.handleContinueButtonMessage(event, options); break; case QUICK_AUTH_MESSAGE_TYPE.Close: this.handleCloseButtonMessage(quickAuthDialogIFrame); break; } } } handleInitializedMessage(event, options) { const { data: { hasSession, popupWidth, popupHeight }, } = event; this.setupQuickAuthIframeHeight({ width: popupWidth, height: popupHeight }); this.isInitialized = true; switch (options.visibility) { case "displayOnInit": this.showWithDelay(); break; case "displayOnInitIfSessionActive": if (hasSession) { this.showWithDelay(); } else { this.hideInstantly(); } break; case "hideOnInit": this.hideInstantly(); break; } if (hasSession) { addAnalyticsDimension("is_op_session_detected", "true"); } } async handleContinueButtonMessage(event, options) { var _a, _b; const { data } = event; const popupSuccessCallback = options.popupSuccessCallback; const popupFailureCallback = options.popupFailureCallback; const authenticationMode = options.authenticationMode; this.clearDismissalData(); const signInOptionsBase = { clientState: options.clientState, disableLoginHintEditing: false, loginHint: data.loginHint, trySilentAuth: data.trySilentAuth, customQueryParameters: options.customQueryParameters, allowUnverifiedEmail: options.allowUnverifiedEmail, }; switch (authenticationMode) { case "popup": try { const signInOptions = { ...signInOptionsBase, redirectUrl: options.redirectUrl, source: "SignInWithOpenPassQuickAuth", }; var authResponse; const openPassApiBaseUrl = getOpenPassApiBaseUrl(this.openPassOptions.baseUrl); if (data.trySilentAuth) { //silent auth let silentAuthResponse = await this.silentAuth.handleSilentAuthWithPopupFallback(this.parentContainer, signInOptions, openPassApiBaseUrl, this.quickAuthDialogIFrame); if (!silentAuthResponse) break; authResponse = silentAuthResponse; } else { // popup // refocus popup if already open if (this.popupAuth.refocusIfPopupExists()) { break; } // otherwise start a new flow authResponse = await this.popupAuth.signInWithPopup(signInOptions); } if (data.trySilentAuth) { (_b = (_a = this.quickAuthDialogIFrame) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage({ type: SILENT_AUTH_OUTGOING_MESSAGE_TYPE.SilentAuthSignInCompletion, }, openPassApiBaseUrl); } if (popupSuccessCallback) { this.hideInstantly(); popupSuccessCallback(authResponse); } } catch (error) { if (error instanceof SdkError) { if (popupFailureCallback) { popupFailureCallback(error); } } else { console.error(error); } } break; case "redirect": this.redirectAuth.signIn({ ...signInOptionsBase, redirectUrl: options.redirectUrl, source: "SignInWithOpenPassQuickAuth", }); break; default: console.error("Invalid authentication mode: " + authenticationMode); break; } } handleCloseButtonMessage(iFrame) { this.incrementDismissalData(); this.hideInstantly(); this.apiClient.sendClientTelemetryEvent("SignInWithOpenPassQuickSignDismissed"); } createHiddenQuickAuthIframe(clientId, parentContainer) { const quickAuthIFrame = document.createElement("iframe"); const openpassBaseUrl = getOpenPassApiBaseUrl(this.openPassOptions.baseUrl); const quickAuthFrameUrl = new URL(`/quick-auth`, openpassBaseUrl); quickAuthFrameUrl.searchParams.append("client_id", clientId); const dismissalData = this.getDismissalData(); if (dismissalData) { quickAuthFrameUrl.searchParams.append("last_dismissed_utc", dismissalData.lastDismissedUtc); quickAuthFrameUrl.searchParams.append("times_dismissed", dismissalData.timesDismissed.toString()); } parentContainer.style.zIndex = "9999"; quickAuthIFrame.src = quickAuthFrameUrl.toString(); quickAuthIFrame.width = "100%"; quickAuthIFrame.style.display = "none"; quickAuthIFrame.style.border = "none"; quickAuthIFrame.style.overflow = "hidden"; const tabletWidth = 640; const browserWidth = window.innerWidth; if (browserWidth > tabletWidth) { parentContainer.style.position = "fixed"; parentContainer.style.top = "120px"; parentContainer.style.right = "20px"; } else { parentContainer.style.width = "100%"; parentContainer.style.position = "fixed"; parentContainer.style.bottom = "0px"; parentContainer.style.left = "0px"; } return quickAuthIFrame; } setupQuickAuthIframeHeight({ width, height }) { if (!this.quickAuthDialogIFrame || !this.parentContainer) { throw new SdkError("Quick Auth is not rendered. Call `render` first."); } this.quickAuthDialogIFrame.height = `${height}px`; this.parentContainer.style.height = `${height}px`; this.quickAuthDialogIFrame.style.minWidth = `${width}px`; } getDismissalData() { const stored = localStorage.getItem(QUICK_AUTH_LOCAL_STORAGE_KEY); if (stored == null) { return null; } try { const dismissalData = JSON.parse(stored); if (!Number.isFinite(dismissalData.timesDismissed) || dismissalData.timesDismissed < 0) { throw new Error("Invalid quick auth dismissal data"); } if (!Date.parse(dismissalData.lastDismissedUtc)) { throw new Error("Invalid quick auth dismissal data"); } return dismissalData; } catch (error) { // Dismissal data has been corrupted this.clearDismissalData(); return null; } } clearDismissalData() { localStorage.removeItem(QUICK_AUTH_LOCAL_STORAGE_KEY); } incrementDismissalData() { const currentDismissalData = this.getDismissalData(); const lastDismissedUtc = new Date().toISOString(); const timesDismissed = ((currentDismissalData === null || currentDismissalData === void 0 ? void 0 : currentDismissalData.timesDismissed) || 0) + 1; localStorage.setItem(QUICK_AUTH_LOCAL_STORAGE_KEY, JSON.stringify({ timesDismissed, lastDismissedUtc })); } } //# sourceMappingURL=quickAuth.js.map