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