UNPKG

@openpass/openpass-js-sdk

Version:
311 lines 17.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const config_1 = require("../config"); const constants_1 = require("./constants"); const errors_1 = require("./error/errors"); const url_1 = require("./url"); /** * Handles rendering and functionality of an inline sign-in form. * * Important: Call cleanup() when the form is no longer needed to remove event listeners. */ class InlineSignInForm { /** * Initializes a new instance of the InlineSignInForm class. * @param openPassOptions - The OpenPass options. * @param redirectAuth - The redirect authentication object. * @param popupAuth - The popup authentication object. */ constructor(openPassOptions, redirectAuth, popupAuth, apiClient, silentAuth) { this.isRedirectingForAuth = false; this.silentAuth = silentAuth; this.openPassOptions = openPassOptions; this.popupAuth = popupAuth; this.redirectAuth = redirectAuth; this.apiClient = apiClient; } /** * Renders the inline sign-in form with the specified options. * @param inlineSignInOptions - The inline sign-in options. * @throws {SdkError} If the required options are missing. */ renderInlineSignInForm(inlineSignInOptions) { var _a; if (!inlineSignInOptions.parentContainerElementId) { throw new errors_1.SdkError("inlineSignInOptions.parentContainerElementId is required for inline sign-in method."); } inlineSignInOptions.signinButtonTextOption = (_a = inlineSignInOptions.signinButtonTextOption) !== null && _a !== void 0 ? _a : "continue"; const iframeContainerHtmlElement = document.getElementById(inlineSignInOptions.parentContainerElementId); const authenticationMode = inlineSignInOptions.authenticationMode; const signinButtonBorderRadiusInPixels = inlineSignInOptions.signinButtonBorderRadiusInPixels; if (!iframeContainerHtmlElement) { throw new errors_1.SdkError(`Cannot locate parent container element "${inlineSignInOptions.parentContainerElementId}" for inline sign-in form.`); } if (authenticationMode != "popup" && authenticationMode != "redirect") { throw new errors_1.SdkError(`Invalid authentication mode: ${authenticationMode}.`); } if (authenticationMode == "redirect" && !inlineSignInOptions.redirectUrl) { throw new errors_1.SdkError("Must provide redirectUrl for inline sign-in form when authentication mode is redirect."); } if (authenticationMode == "popup" && !inlineSignInOptions.popupSuccessCallback) { throw new errors_1.SdkError("Must provide popupSuccessCallback for inline sign-in form when authentication mode is popup."); } if (inlineSignInOptions.widthInPixels && inlineSignInOptions.widthInPixels < 250) { throw new errors_1.SdkError("Inline sign-in form width must be at least 250 pixels."); } if (inlineSignInOptions.heightInPixels && inlineSignInOptions.heightInPixels < 500) { throw new errors_1.SdkError("Inline sign-in form width must be at least 500 pixels."); } if (!["boolean", "undefined"].includes(typeof inlineSignInOptions.darkModeEnabled)) { throw new errors_1.SdkError("Invalid darkModeEnabled value. Must be true or false."); } if (typeof signinButtonBorderRadiusInPixels !== "undefined") { if (!/^[0-9]+$/.test(String(signinButtonBorderRadiusInPixels))) { throw new errors_1.SdkError("Invalid signinButtonBorderRadiusInPixels value. Must be an integer number greater than or equal to 0."); } else if (signinButtonBorderRadiusInPixels > constants_1.API_INTEGER_MAX_VALUE) { throw new errors_1.SdkError(`Invalid signinButtonBorderRadiusInPixels value. Must be less than or equal to ${constants_1.API_INTEGER_MAX_VALUE}.`); } } const popupSuccessCallback = inlineSignInOptions.popupSuccessCallback; const popupFailureCallback = inlineSignInOptions.popupFailureCallback; const popupSignInInitiatedCallback = inlineSignInOptions.popupSignInInitiatedCallback; const signInMessageHandler = async (event) => { var _a, _b, _c, _d; const openPassApiBaseUrl = (0, config_1.getOpenPassApiBaseUrl)(this.openPassOptions.baseUrl); //Check that the message is correct and came from the OpenPass API inline sign in form if (!(0, url_1.matchesEventOrigin)(event.origin, openPassApiBaseUrl) || !event.data || event.data.type != "inline-sign-in-message") { return; } const messageData = event.data; const authenticationPredictionId = messageData.authenticationPredictionId; const customQueryParameters = (_b = (_a = inlineSignInOptions.customQueryParameters) === null || _a === void 0 ? void 0 : _a.slice()) !== null && _b !== void 0 ? _b : []; if (authenticationPredictionId) { customQueryParameters.push({ name: "authentication_prediction_id", value: authenticationPredictionId }); } let clientState = inlineSignInOptions.clientState; if (messageData.additionalClientState !== undefined) { clientState = { ...clientState, ...messageData.additionalClientState, }; } const signInOptionsBase = { clientState, disableLoginHintEditing: false, loginHint: messageData.loginHint, customQueryParameters: customQueryParameters, allowUnverifiedEmail: inlineSignInOptions.allowUnverifiedEmail, trySilentAuth: messageData.trySilentAuth, }; switch (authenticationMode) { case "popup": try { let authResponse; if (messageData.trySilentAuth) { const silentAuthResponse = await this.silentAuth.handleSilentAuthWithPopupFallback(iframeContainerHtmlElement, { ...signInOptionsBase, redirectUrl: inlineSignInOptions.redirectUrl, source: "SignInWithOpenPassInlineForm", customQueryParameters, }, openPassApiBaseUrl, iframe, async () => this.handlePopupFlow(messageData, signInOptionsBase, inlineSignInOptions, customQueryParameters, popupSignInInitiatedCallback)); if (!silentAuthResponse) break; authResponse = silentAuthResponse; } else { if (this.currentLoginHint === messageData.loginHint && this.popupAuth.refocusIfPopupExists()) { // Set up beforeunload handler even when refocusing existing popup this.setupBeforeUnloadHandler(); break; } authResponse = await this.handlePopupFlow(messageData, signInOptionsBase, inlineSignInOptions, customQueryParameters, popupSignInInitiatedCallback); } if (messageData.trySilentAuth) { (_c = iframe.contentWindow) === null || _c === void 0 ? void 0 : _c.postMessage({ type: constants_1.SILENT_AUTH_OUTGOING_MESSAGE_TYPE.SilentAuthSignInCompletion, }, openPassApiBaseUrl); } // Remove beforeunload handler since popup flow completed successfully this.cleanup(); if (popupSuccessCallback) { popupSuccessCallback(authResponse); } } catch (error) { // Remove beforeunload handler since popup flow failed this.cleanup(); if (error instanceof errors_1.SdkError) { if (popupFailureCallback) { popupFailureCallback(error); } } else { console.error(error); } } break; case "redirect": // stop processing auth messages once we initiated sign in redirect if (this.isRedirectingForAuth) { break; } this.isRedirectingForAuth = true; try { await this.redirectAuth.signIn({ ...signInOptionsBase, redirectUrl: inlineSignInOptions.redirectUrl, source: "SignInWithOpenPassInlineForm", customQueryParameters, }); } catch (error) { (_d = iframe.contentWindow) === null || _d === void 0 ? void 0 : _d.postMessage({ type: constants_1.INLINE_SIGN_IN_OUTGOING_MESSAGE_TYPE.RedirectSignInFailure, }, openPassApiBaseUrl); console.error(error); } finally { this.isRedirectingForAuth = false; } break; default: console.log("Invalid authentication mode: " + authenticationMode); break; } }; window.addEventListener("message", signInMessageHandler); const iframe = this.createIframeElement(this.openPassOptions.clientId, inlineSignInOptions); iframeContainerHtmlElement.appendChild(iframe); // Send telemetry event // Do not await the result, this is a fire and forget operation this.apiClient.sendClientTelemetryEvent("SignInWithOpenPassInlineFormShown"); } async handlePopupFlow(messageData, signInOptionsBase, inlineSignInOptions, customQueryParameters, popupSignInInitiatedCallback) { if (popupSignInInitiatedCallback) { try { popupSignInInitiatedCallback(); } catch (error) { console.error("Error handling sign-in initiated callback.", error); } } // Set up beforeunload handler to prevent page closure during popup flow this.setupBeforeUnloadHandler(); try { // otherwise start a new flow this.currentLoginHint = messageData.loginHint; const result = await this.popupAuth.signInWithPopup({ ...signInOptionsBase, redirectUrl: inlineSignInOptions.redirectUrl, source: "SignInWithOpenPassInlineForm", customQueryParameters, }); // Remove beforeunload handler on successful completion this.cleanup(); return result; } catch (error) { // Remove beforeunload handler on error this.cleanup(); throw error; } } /** * Sets up the beforeunload event handler to prevent page closure when popup is active and tab is hidden */ setupBeforeUnloadHandler() { if (this.beforeUnloadHandler) { return; // Handler already set up } this.beforeUnloadHandler = (event) => { // Check if there's an active popup if (this.popupAuth.hasActivePopup()) { event.preventDefault(); } }; this.visibilityChangeHandler = () => { if (document.hidden) { // Tab is now hidden/unfocused - add beforeunload handler if (this.beforeUnloadHandler) { console.log("adding beforeunload handler"); this.popupAuth.removePopupCloseOnBeforeUnloadHandler(); window.addEventListener("beforeunload", this.beforeUnloadHandler); } } else { // Tab is now visible/focused - remove beforeunload handler if (this.beforeUnloadHandler) { console.log("removing beforeunload handler"); this.popupAuth.addPopupCloseOnBeforeUnloadHandler(); window.removeEventListener("beforeunload", this.beforeUnloadHandler); } } }; // Set up visibility change listener document.addEventListener("visibilitychange", this.visibilityChangeHandler); // Only add beforeunload handler if document is currently hidden if (document.hidden) { console.log("adding beforeunload handler on setup"); this.popupAuth.removePopupCloseOnBeforeUnloadHandler(); window.addEventListener("beforeunload", this.beforeUnloadHandler); } } /** * Removes the beforeunload event handler and visibility change listener */ removeBeforeUnloadAndVisibilityHandlers() { console.log("removing beforeunload handler"); if (this.beforeUnloadHandler) { window.removeEventListener("beforeunload", this.beforeUnloadHandler); this.beforeUnloadHandler = undefined; } if (this.visibilityChangeHandler) { document.removeEventListener("visibilitychange", this.visibilityChangeHandler); this.visibilityChangeHandler = undefined; } } /** * Cleanup method to remove all event handlers when the inline sign-in form is no longer needed. * This should be called when: * - The component/page containing the inline sign-in form is unmounted * - The user navigates away from the page * - The inline sign-in form is no longer being used * * Note: This is automatically called when popup authentication completes or fails, * but should be manually called in other cleanup scenarios. */ cleanup() { this.removeBeforeUnloadAndVisibilityHandlers(); } createIframeElement(clientId, inlineSignInOptions) { var _a, _b, _c, _d, _e, _f; const inlineSignInIFrame = document.createElement("iframe"); const openpassBaseUrl = (0, config_1.getOpenPassApiBaseUrl)(this.openPassOptions.baseUrl); const inlineSignInFrameUrl = new URL(`/inline-sign-in-v2`, openpassBaseUrl); inlineSignInFrameUrl.searchParams.append("client_id", clientId); inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_SIGN_IN_BUTTON_TEXT, (_b = (_a = inlineSignInOptions.signinButtonTextOption) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "continue"); inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_AUTH_MODE, inlineSignInOptions.authenticationMode); if (inlineSignInOptions.hideSignInFormApplicationLogo) { inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_HIDE_CLIENT_APPLICATION_LOGO, inlineSignInOptions.hideSignInFormApplicationLogo.toString()); } if (inlineSignInOptions.hideSignInFormHeaderText) { inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_HIDE_HEADER_TEXT, inlineSignInOptions.hideSignInFormHeaderText.toString()); } if (inlineSignInOptions.signinButtonBackgroundColorHex) { inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_SIGN_IN_BUTTON_BACKGROUND_HEX_COLOUR, inlineSignInOptions.signinButtonBackgroundColorHex); } if (typeof inlineSignInOptions.signinButtonBorderRadiusInPixels !== "undefined") { inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_SIGN_IN_BUTTON_BORDER_RADIUS_PX, String(inlineSignInOptions.signinButtonBorderRadiusInPixels)); } if (inlineSignInOptions.darkModeEnabled) { inlineSignInFrameUrl.searchParams.append(constants_1.PARAM_CODE_INLINE_SIGN_IN_FORM_DARK_MODE, inlineSignInOptions.darkModeEnabled.toString()); } inlineSignInIFrame.src = inlineSignInFrameUrl.toString(); inlineSignInIFrame.width = (_d = (_c = inlineSignInOptions.widthInPixels) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : "100%"; inlineSignInIFrame.height = (_f = (_e = inlineSignInOptions.heightInPixels) === null || _e === void 0 ? void 0 : _e.toString()) !== null && _f !== void 0 ? _f : "100%"; return inlineSignInIFrame; } } exports.default = InlineSignInForm; //# sourceMappingURL=inlineSignInForm.js.map