diff --git a/packages/passport/sdk/src/config/config.ts b/packages/passport/sdk/src/config/config.ts index 9d90ee9120..4d5e1fed60 100644 --- a/packages/passport/sdk/src/config/config.ts +++ b/packages/passport/sdk/src/config/config.ts @@ -3,6 +3,7 @@ import { createConfig, MultiRollupAPIConfiguration, multiRollupConfig } from '@i import { OidcConfiguration, PassportModuleConfiguration, + PopupOverlayOptions, } from '../types'; import { PassportError, PassportErrorType } from '../errors/passportError'; @@ -49,10 +50,13 @@ export class PassportConfiguration { readonly crossSdkBridgeEnabled: boolean; + readonly popupOverlayOptions: PopupOverlayOptions; + constructor({ baseConfig, overrides, crossSdkBridgeEnabled, + popupOverlayOptions, ...oidcConfiguration }: PassportModuleConfiguration) { validateConfiguration(oidcConfiguration, [ @@ -62,6 +66,10 @@ export class PassportConfiguration { this.oidcConfiguration = oidcConfiguration; this.baseConfig = baseConfig; this.crossSdkBridgeEnabled = crossSdkBridgeEnabled || false; + this.popupOverlayOptions = popupOverlayOptions || { + disableGenericPopupOverlay: false, + disableBlockedPopupOverlay: false, + }; if (overrides) { validateConfiguration( diff --git a/packages/passport/sdk/src/confirmation/confirmation.ts b/packages/passport/sdk/src/confirmation/confirmation.ts index 2e8db452a6..65c4993b49 100644 --- a/packages/passport/sdk/src/confirmation/confirmation.ts +++ b/packages/passport/sdk/src/confirmation/confirmation.ts @@ -7,6 +7,7 @@ import { } from './types'; import { openPopupCenter } from './popup'; import { PassportConfiguration } from '../config'; +import Overlay from '../overlay/overlay'; const CONFIRMATION_WINDOW_TITLE = 'Confirm this transaction'; const CONFIRMATION_WINDOW_HEIGHT = 720; @@ -23,8 +24,17 @@ export default class ConfirmationScreen { private confirmationWindow: Window | undefined; + private popupOptions: { width: number; height: number } | undefined; + + private overlay: Overlay | undefined; + + private overlayClosed: boolean; + + private timer: NodeJS.Timeout | undefined; + constructor(config: PassportConfiguration) { this.config = config; + this.overlayClosed = false; } private getHref(relativePath: string, queryStringParams?: { [key: string]: any }) { @@ -57,6 +67,7 @@ export default class ConfirmationScreen { ) { return; } + switch (data.messageType as ReceiveMessage) { case ReceiveMessage.CONFIRMATION_WINDOW_READY: { this.confirmationWindow?.postMessage({ @@ -66,26 +77,25 @@ export default class ConfirmationScreen { break; } case ReceiveMessage.TRANSACTION_CONFIRMED: { + this.closeWindow(); resolve({ confirmed: true }); break; } case ReceiveMessage.TRANSACTION_ERROR: { + this.closeWindow(); reject(new Error('Error during transaction confirmation')); break; } case ReceiveMessage.TRANSACTION_REJECTED: { + this.closeWindow(); reject(new Error('User rejected transaction')); break; } default: + this.closeWindow(); reject(new Error('Unsupported message type')); } }; - if (!this.confirmationWindow) { - resolve({ confirmed: false }); - return; - } - window.addEventListener('message', messageHandler); let href = ''; if (chainType === TransactionApprovalRequestChainTypeEnum.Starkex) { @@ -95,6 +105,7 @@ export default class ConfirmationScreen { transactionID: transactionId, etherAddress, chainType, chainID: chainId, }); } + window.addEventListener('message', messageHandler); this.showConfirmationScreen(href, messageHandler, resolve); }); } @@ -117,26 +128,26 @@ export default class ConfirmationScreen { break; } case ReceiveMessage.MESSAGE_CONFIRMED: { + this.closeWindow(); resolve({ confirmed: true }); break; } case ReceiveMessage.MESSAGE_ERROR: { + this.closeWindow(); reject(new Error('Error during message confirmation')); break; } case ReceiveMessage.MESSAGE_REJECTED: { + this.closeWindow(); reject(new Error('User rejected message')); break; } - default: + this.closeWindow(); reject(new Error('Unsupported message type')); } }; - if (!this.confirmationWindow) { - resolve({ confirmed: false }); - return; - } + window.addEventListener('message', messageHandler); const href = this.getHref('zkevm/message', { messageID, etherAddress }); this.showConfirmationScreen(href, messageHandler, resolve); @@ -149,27 +160,89 @@ export default class ConfirmationScreen { return; } - this.confirmationWindow = openPopupCenter({ - url: this.getHref('loading'), - title: CONFIRMATION_WINDOW_TITLE, - width: popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, - height: popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, - }); + this.popupOptions = popupOptions; + + try { + this.confirmationWindow = openPopupCenter({ + url: this.getHref('loading'), + title: CONFIRMATION_WINDOW_TITLE, + width: popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, + height: popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, + }); + this.overlay = new Overlay(this.config.popupOverlayOptions); + } catch (e) { + // If an error is thrown here then the popup is blocked + this.overlay = new Overlay(this.config.popupOverlayOptions, true); + } + + this.overlay.append( + () => { + try { + this.confirmationWindow?.close(); + this.confirmationWindow = openPopupCenter({ + url: this.getHref('loading'), + title: CONFIRMATION_WINDOW_TITLE, + width: this.popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, + height: this.popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, + }); + } catch { /* Empty */ } + }, + () => { + this.overlayClosed = true; + this.closeWindow(); + }, + ); } closeWindow() { this.confirmationWindow?.close(); + this.overlay?.remove(); + this.overlay = undefined; } showConfirmationScreen(href: string, messageHandler: MessageHandler, resolve: Function) { - this.confirmationWindow!.location.href = href; + // If popup blocked, the confirmation window will not exist + if (this.confirmationWindow) { + this.confirmationWindow.location.href = href; + } + + // This indicates the user closed the overlay so the transaction should be rejected + if (!this.overlay) { + this.overlayClosed = false; + resolve({ confirmed: false }); + return; + } + // https://stackoverflow.com/questions/9388380/capture-the-close-event-of-popup-window-in-javascript/48240128#48240128 - const timer = setInterval(() => { - if (this.confirmationWindow?.closed) { - clearInterval(timer); + const timerCallback = () => { + if (this.confirmationWindow?.closed || this.overlayClosed) { + clearInterval(this.timer); window.removeEventListener('message', messageHandler); resolve({ confirmed: false }); + this.overlayClosed = false; + this.confirmationWindow = undefined; } - }, CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION); + }; + this.timer = setInterval( + timerCallback, + CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION, + ); + this.overlay.update(() => this.recreateConfirmationWindow(href, timerCallback)); + } + + private recreateConfirmationWindow(href: string, timerCallback: () => void) { + try { + // Clears and recreates the timer to ensure when the confirmation window + // is closed and recreated the transaction is not rejected. + clearInterval(this.timer); + this.confirmationWindow?.close(); + this.confirmationWindow = openPopupCenter({ + url: href, + title: CONFIRMATION_WINDOW_TITLE, + width: this.popupOptions?.width || CONFIRMATION_WINDOW_WIDTH, + height: this.popupOptions?.height || CONFIRMATION_WINDOW_HEIGHT, + }); + this.timer = setInterval(timerCallback, CONFIRMATION_WINDOW_CLOSED_POLLING_DURATION); + } catch { /* Empty */ } } } diff --git a/packages/passport/sdk/src/overlay/constants.ts b/packages/passport/sdk/src/overlay/constants.ts new file mode 100644 index 0000000000..741379bba7 --- /dev/null +++ b/packages/passport/sdk/src/overlay/constants.ts @@ -0,0 +1,220 @@ +/* eslint-disable max-len */ + +export const PASSPORT_OVERLAY_ID = 'passport-overlay'; +export const PASSPORT_OVERLAY_CLOSE_ID = `${PASSPORT_OVERLAY_ID}-close`; +export const PASSPORT_OVERLAY_TRY_AGAIN_ID = `${PASSPORT_OVERLAY_ID}-try-again`; + +export const CLOSE_BUTTON_SVG = ` + +`; + +export const POPUP_BLOCKED_SVG = ` + +`; + +export const IMMUTABLE_LOGO_SVG = ` + +`; diff --git a/packages/passport/sdk/src/overlay/elements.ts b/packages/passport/sdk/src/overlay/elements.ts new file mode 100644 index 0000000000..9d8d0c3d0b --- /dev/null +++ b/packages/passport/sdk/src/overlay/elements.ts @@ -0,0 +1,126 @@ +import { + CLOSE_BUTTON_SVG, + POPUP_BLOCKED_SVG, + IMMUTABLE_LOGO_SVG, + PASSPORT_OVERLAY_CLOSE_ID, + PASSPORT_OVERLAY_ID, + PASSPORT_OVERLAY_TRY_AGAIN_ID, +} from './constants'; + +const getCloseButton = (): string => ` + + `; + +const getBlockedContents = () => ` +
+ Please adjust your browser settings
and try again below
+
+ Secure pop-up not showing?
We'll help you re-launch
+