@openpass/openpass-js-sdk
Version:
OpenPass SSO JavaScript SDK
311 lines • 17.2 kB
JavaScript
"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