From 5f11da8cbfe35840bfaa0f0b6972481f1066f32f Mon Sep 17 00:00:00 2001 From: kwojtasinski-repo Date: Fri, 25 Aug 2023 18:31:42 +0200 Subject: [PATCH 1/3] #1134 Add possibility to automatically close popup window after some time --- docs/oidc-client-ts.api.md | 4 ++++ src/UserManagerSettings.test.ts | 28 +++++++++++++++++++++++++++- src/UserManagerSettings.ts | 14 +++++++++++++- src/navigators/PopupWindow.ts | 3 +++ src/utils/PopupUtils.ts | 2 ++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index a7412c74d..7227f2f67 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -481,6 +481,10 @@ export interface PopupWindowFeatures { // (undocumented) [k: string]: boolean | string | number | undefined; // (undocumented) + closeAutomaticallyPopupWindow?: boolean; + // (undocumented) + closePopupWindowAfter?: number; + // (undocumented) height?: number; // (undocumented) left?: number; diff --git a/src/UserManagerSettings.test.ts b/src/UserManagerSettings.test.ts index 310dc3c27..6aea50f65 100644 --- a/src/UserManagerSettings.test.ts +++ b/src/UserManagerSettings.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { UserManagerSettingsStore } from "./UserManagerSettings"; +import { DefaultPopupWindowFeatures, UserManagerSettingsStore } from "./UserManagerSettings"; import type { WebStorageStateStore } from "./WebStorageStateStore"; describe("UserManagerSettings", () => { @@ -53,6 +53,32 @@ describe("UserManagerSettings", () => { expect(subject.popupWindowFeatures).toEqual({ status: true }); }); + it("should validate popup parameter when closePopupWindowAfter is equal to 0 and closeAutomaticallyPopupWindow is enabled should set default value", () => { + // act + const subject = new UserManagerSettingsStore({ + authority: "authority", + client_id: "client", + redirect_uri: "redirect", + popupWindowFeatures: { status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: 0 }, + }); + + // assert + expect(subject.popupWindowFeatures).toEqual({ status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: DefaultPopupWindowFeatures.closePopupWindowAfter }); + }); + + it("should validate popup parameter when closePopupWindowAfter is negative and closeAutomaticallyPopupWindow is enabled should set default value", () => { + // act + const subject = new UserManagerSettingsStore({ + authority: "authority", + client_id: "client", + redirect_uri: "redirect", + popupWindowFeatures: { status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: -1 }, + }); + + // assert + expect(subject.popupWindowFeatures).toEqual({ status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: DefaultPopupWindowFeatures.closePopupWindowAfter }); + }); + }); describe("popupWindowTarget", () => { diff --git a/src/UserManagerSettings.ts b/src/UserManagerSettings.ts index e2c9045aa..6e874516c 100644 --- a/src/UserManagerSettings.ts +++ b/src/UserManagerSettings.ts @@ -10,6 +10,8 @@ export const DefaultPopupWindowFeatures: PopupWindowFeatures = { location: false, toolbar: false, height: 640, + closeAutomaticallyPopupWindow: false, + closePopupWindowAfter: 600000, }; export const DefaultPopupTarget = "_blank"; const DefaultAccessTokenExpiringNotificationTimeInSeconds = 60; @@ -28,7 +30,7 @@ export interface UserManagerSettings extends OidcClientSettings { /** * The features parameter to window.open for the popup signin window. By default, the popup is * placed centered in front of the window opener. - * (default: \{ location: false, menubar: false, height: 640 \}) + * (default: \{ location: false, menubar: false, height: 640, closeAutomaticallyPopupWindow: false, closePopupWindowAfter: 600000 \}) */ popupWindowFeatures?: PopupWindowFeatures; /** The target parameter to window.open for the popup signin window (default: "_blank") */ @@ -158,6 +160,7 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { this.popup_redirect_uri = popup_redirect_uri; this.popup_post_logout_redirect_uri = popup_post_logout_redirect_uri; + this.validatePopupWindowFeatures(popupWindowFeatures); this.popupWindowFeatures = popupWindowFeatures; this.popupWindowTarget = popupWindowTarget; this.redirectMethod = redirectMethod; @@ -192,4 +195,13 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { this.userStore = new WebStorageStateStore({ store }); } } + + private validatePopupWindowFeatures(popupWindowFeatures: PopupWindowFeatures): void { + if (popupWindowFeatures.closeAutomaticallyPopupWindow) { + if (popupWindowFeatures.closePopupWindowAfter !== undefined && popupWindowFeatures.closePopupWindowAfter !== null && popupWindowFeatures.closePopupWindowAfter <= 0) { + console.log("validatePopupWindowFeatures"); + popupWindowFeatures.closePopupWindowAfter = DefaultPopupWindowFeatures.closePopupWindowAfter; + } + } + } } diff --git a/src/navigators/PopupWindow.ts b/src/navigators/PopupWindow.ts index f79dd786a..69d18d398 100644 --- a/src/navigators/PopupWindow.ts +++ b/src/navigators/PopupWindow.ts @@ -31,6 +31,9 @@ export class PopupWindow extends AbstractChildWindow { super(); const centeredPopup = PopupUtils.center({ ...DefaultPopupWindowFeatures, ...popupWindowFeatures }); this._window = window.open(undefined, popupWindowTarget, PopupUtils.serialize(centeredPopup)); + if (popupWindowFeatures.closeAutomaticallyPopupWindow) { + setTimeout(() => { this.close(); }, popupWindowFeatures.closePopupWindowAfter); + } } public async navigate(params: NavigateParams): Promise { diff --git a/src/utils/PopupUtils.ts b/src/utils/PopupUtils.ts index fcd2335a5..4ffa7ee32 100644 --- a/src/utils/PopupUtils.ts +++ b/src/utils/PopupUtils.ts @@ -14,6 +14,8 @@ export interface PopupWindowFeatures { status?: boolean | string; resizable?: boolean | string; scrollbars?: boolean | string; + closeAutomaticallyPopupWindow?: boolean; + closePopupWindowAfter?: number; [k: string]: boolean | string | number | undefined; } From 98a35a4e9a41dee41f18252131172686a0970962 Mon Sep 17 00:00:00 2001 From: KW Date: Wed, 30 Aug 2023 19:22:33 +0200 Subject: [PATCH 2/3] #1134 Add possibility to automatically close popup window after some time PR Feedback --- docs/oidc-client-ts.api.md | 3 --- src/UserManagerSettings.test.ts | 23 +++++------------------ src/UserManagerSettings.ts | 15 ++------------- src/navigators/PopupWindow.test.ts | 28 ++++++++++++++++++++++++++++ src/navigators/PopupWindow.ts | 8 ++++++-- src/utils/PopupUtils.ts | 2 +- 6 files changed, 42 insertions(+), 37 deletions(-) diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index 7227f2f67..036d73ea2 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -480,9 +480,6 @@ export interface OidcStandardClaims { export interface PopupWindowFeatures { // (undocumented) [k: string]: boolean | string | number | undefined; - // (undocumented) - closeAutomaticallyPopupWindow?: boolean; - // (undocumented) closePopupWindowAfter?: number; // (undocumented) height?: number; diff --git a/src/UserManagerSettings.test.ts b/src/UserManagerSettings.test.ts index 6aea50f65..26ee49547 100644 --- a/src/UserManagerSettings.test.ts +++ b/src/UserManagerSettings.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { DefaultPopupWindowFeatures, UserManagerSettingsStore } from "./UserManagerSettings"; +import { UserManagerSettingsStore } from "./UserManagerSettings"; import type { WebStorageStateStore } from "./WebStorageStateStore"; describe("UserManagerSettings", () => { @@ -53,32 +53,19 @@ describe("UserManagerSettings", () => { expect(subject.popupWindowFeatures).toEqual({ status: true }); }); - it("should validate popup parameter when closePopupWindowAfter is equal to 0 and closeAutomaticallyPopupWindow is enabled should set default value", () => { + it("should set closePopupWindowAfter", () => { // act + const closePopupWindowAfter = 100; const subject = new UserManagerSettingsStore({ authority: "authority", client_id: "client", redirect_uri: "redirect", - popupWindowFeatures: { status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: 0 }, + popupWindowFeatures: { status: true, closePopupWindowAfter }, }); // assert - expect(subject.popupWindowFeatures).toEqual({ status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: DefaultPopupWindowFeatures.closePopupWindowAfter }); + expect(subject.popupWindowFeatures).toEqual({ status: true, closePopupWindowAfter }); }); - - it("should validate popup parameter when closePopupWindowAfter is negative and closeAutomaticallyPopupWindow is enabled should set default value", () => { - // act - const subject = new UserManagerSettingsStore({ - authority: "authority", - client_id: "client", - redirect_uri: "redirect", - popupWindowFeatures: { status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: -1 }, - }); - - // assert - expect(subject.popupWindowFeatures).toEqual({ status: true, closeAutomaticallyPopupWindow: true, closePopupWindowAfter: DefaultPopupWindowFeatures.closePopupWindowAfter }); - }); - }); describe("popupWindowTarget", () => { diff --git a/src/UserManagerSettings.ts b/src/UserManagerSettings.ts index 6e874516c..18c67d554 100644 --- a/src/UserManagerSettings.ts +++ b/src/UserManagerSettings.ts @@ -10,8 +10,7 @@ export const DefaultPopupWindowFeatures: PopupWindowFeatures = { location: false, toolbar: false, height: 640, - closeAutomaticallyPopupWindow: false, - closePopupWindowAfter: 600000, + closePopupWindowAfter: 0, }; export const DefaultPopupTarget = "_blank"; const DefaultAccessTokenExpiringNotificationTimeInSeconds = 60; @@ -30,7 +29,7 @@ export interface UserManagerSettings extends OidcClientSettings { /** * The features parameter to window.open for the popup signin window. By default, the popup is * placed centered in front of the window opener. - * (default: \{ location: false, menubar: false, height: 640, closeAutomaticallyPopupWindow: false, closePopupWindowAfter: 600000 \}) + * (default: \{ location: false, menubar: false, height: 640, closePopupWindowAfter: 0 \}) */ popupWindowFeatures?: PopupWindowFeatures; /** The target parameter to window.open for the popup signin window (default: "_blank") */ @@ -160,7 +159,6 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { this.popup_redirect_uri = popup_redirect_uri; this.popup_post_logout_redirect_uri = popup_post_logout_redirect_uri; - this.validatePopupWindowFeatures(popupWindowFeatures); this.popupWindowFeatures = popupWindowFeatures; this.popupWindowTarget = popupWindowTarget; this.redirectMethod = redirectMethod; @@ -195,13 +193,4 @@ export class UserManagerSettingsStore extends OidcClientSettingsStore { this.userStore = new WebStorageStateStore({ store }); } } - - private validatePopupWindowFeatures(popupWindowFeatures: PopupWindowFeatures): void { - if (popupWindowFeatures.closeAutomaticallyPopupWindow) { - if (popupWindowFeatures.closePopupWindowAfter !== undefined && popupWindowFeatures.closePopupWindowAfter !== null && popupWindowFeatures.closePopupWindowAfter <= 0) { - console.log("validatePopupWindowFeatures"); - popupWindowFeatures.closePopupWindowAfter = DefaultPopupWindowFeatures.closePopupWindowAfter; - } - } - } } diff --git a/src/navigators/PopupWindow.test.ts b/src/navigators/PopupWindow.test.ts index 32211b805..163d03bcb 100644 --- a/src/navigators/PopupWindow.test.ts +++ b/src/navigators/PopupWindow.test.ts @@ -144,4 +144,32 @@ describe("PopupWindow", () => { keepOpen: false, }, window.location.origin); }); + + it("should close the window after closePopupWindowAfter is greater than 0", async () => { + const popupWindow = new PopupWindow({ popupWindowFeatures: { closePopupWindowAfter: 1 } }); + + const promise = popupWindow.navigate({ url: "http://sts/authorize?x=y", state: "someid" }); + + jest.runOnlyPendingTimers(); + await expect(promise).rejects.toThrow("Popup blocked by user"); + jest.runAllTimers(); + }); + + it("shouldnt close the window after closePopupWindowAfter is equal to 0", async () => { + jest.spyOn(global, "setTimeout"); + + new PopupWindow({ popupWindowFeatures: { closePopupWindowAfter: 0 } }); + + jest.runOnlyPendingTimers(); + expect(setTimeout).toHaveBeenCalledTimes(0); + }); + + it("shouldnt close the window after closePopupWindowAfter is less than 0", async () => { + jest.spyOn(global, "setTimeout"); + + new PopupWindow({ popupWindowFeatures: { closePopupWindowAfter: -120 } }); + + jest.runOnlyPendingTimers(); + expect(setTimeout).toHaveBeenCalledTimes(0); + }); }); diff --git a/src/navigators/PopupWindow.ts b/src/navigators/PopupWindow.ts index 69d18d398..b780e680e 100644 --- a/src/navigators/PopupWindow.ts +++ b/src/navigators/PopupWindow.ts @@ -31,8 +31,12 @@ export class PopupWindow extends AbstractChildWindow { super(); const centeredPopup = PopupUtils.center({ ...DefaultPopupWindowFeatures, ...popupWindowFeatures }); this._window = window.open(undefined, popupWindowTarget, PopupUtils.serialize(centeredPopup)); - if (popupWindowFeatures.closeAutomaticallyPopupWindow) { - setTimeout(() => { this.close(); }, popupWindowFeatures.closePopupWindowAfter); + if (popupWindowFeatures?.closePopupWindowAfter && popupWindowFeatures?.closePopupWindowAfter > 0) { + setTimeout(() => { + if (!this._window || typeof this._window.closed !== "boolean" || this._window.closed) { + this._abort.raise(new Error("Popup blocked by user")); + } + }, popupWindowFeatures.closePopupWindowAfter); } } diff --git a/src/utils/PopupUtils.ts b/src/utils/PopupUtils.ts index 4ffa7ee32..d48291942 100644 --- a/src/utils/PopupUtils.ts +++ b/src/utils/PopupUtils.ts @@ -14,7 +14,7 @@ export interface PopupWindowFeatures { status?: boolean | string; resizable?: boolean | string; scrollbars?: boolean | string; - closeAutomaticallyPopupWindow?: boolean; + /** Close popup window after time in milliseconds, by default it is 0. To enable this feature set value greater than 0 */ closePopupWindowAfter?: number; [k: string]: boolean | string | number | undefined; From 10b57ab5f28138451394ee8541adb74f24634eff Mon Sep 17 00:00:00 2001 From: KW Date: Thu, 31 Aug 2023 23:45:19 +0200 Subject: [PATCH 3/3] #1134 Add possibility to automatically close popup window after some time PR Feedback --- docs/oidc-client-ts.api.md | 2 +- src/UserManagerSettings.test.ts | 8 ++++---- src/UserManagerSettings.ts | 4 ++-- src/navigators/PopupWindow.test.ts | 6 +++--- src/navigators/PopupWindow.ts | 5 +++-- src/utils/PopupUtils.ts | 4 ++-- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index 036d73ea2..a7b7a2374 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -480,7 +480,7 @@ export interface OidcStandardClaims { export interface PopupWindowFeatures { // (undocumented) [k: string]: boolean | string | number | undefined; - closePopupWindowAfter?: number; + closePopupWindowAfterInSeconds?: number; // (undocumented) height?: number; // (undocumented) diff --git a/src/UserManagerSettings.test.ts b/src/UserManagerSettings.test.ts index 26ee49547..e1e3cb397 100644 --- a/src/UserManagerSettings.test.ts +++ b/src/UserManagerSettings.test.ts @@ -53,18 +53,18 @@ describe("UserManagerSettings", () => { expect(subject.popupWindowFeatures).toEqual({ status: true }); }); - it("should set closePopupWindowAfter", () => { + it("should set closePopupWindowAfterInMilliseconds", () => { // act - const closePopupWindowAfter = 100; + const closePopupWindowAfterInSeconds = 100; const subject = new UserManagerSettingsStore({ authority: "authority", client_id: "client", redirect_uri: "redirect", - popupWindowFeatures: { status: true, closePopupWindowAfter }, + popupWindowFeatures: { status: true, closePopupWindowAfterInSeconds }, }); // assert - expect(subject.popupWindowFeatures).toEqual({ status: true, closePopupWindowAfter }); + expect(subject.popupWindowFeatures).toEqual({ status: true, closePopupWindowAfterInSeconds }); }); }); diff --git a/src/UserManagerSettings.ts b/src/UserManagerSettings.ts index 18c67d554..cf6ae3662 100644 --- a/src/UserManagerSettings.ts +++ b/src/UserManagerSettings.ts @@ -10,7 +10,7 @@ export const DefaultPopupWindowFeatures: PopupWindowFeatures = { location: false, toolbar: false, height: 640, - closePopupWindowAfter: 0, + closePopupWindowAfterInSeconds: -1, }; export const DefaultPopupTarget = "_blank"; const DefaultAccessTokenExpiringNotificationTimeInSeconds = 60; @@ -29,7 +29,7 @@ export interface UserManagerSettings extends OidcClientSettings { /** * The features parameter to window.open for the popup signin window. By default, the popup is * placed centered in front of the window opener. - * (default: \{ location: false, menubar: false, height: 640, closePopupWindowAfter: 0 \}) + * (default: \{ location: false, menubar: false, height: 640, closePopupWindowAfterInSeconds: -1 \}) */ popupWindowFeatures?: PopupWindowFeatures; /** The target parameter to window.open for the popup signin window (default: "_blank") */ diff --git a/src/navigators/PopupWindow.test.ts b/src/navigators/PopupWindow.test.ts index 163d03bcb..aa2078a77 100644 --- a/src/navigators/PopupWindow.test.ts +++ b/src/navigators/PopupWindow.test.ts @@ -146,7 +146,7 @@ describe("PopupWindow", () => { }); it("should close the window after closePopupWindowAfter is greater than 0", async () => { - const popupWindow = new PopupWindow({ popupWindowFeatures: { closePopupWindowAfter: 1 } }); + const popupWindow = new PopupWindow({ popupWindowFeatures: { closePopupWindowAfterInSeconds: 1 } }); const promise = popupWindow.navigate({ url: "http://sts/authorize?x=y", state: "someid" }); @@ -158,7 +158,7 @@ describe("PopupWindow", () => { it("shouldnt close the window after closePopupWindowAfter is equal to 0", async () => { jest.spyOn(global, "setTimeout"); - new PopupWindow({ popupWindowFeatures: { closePopupWindowAfter: 0 } }); + new PopupWindow({ popupWindowFeatures: { closePopupWindowAfterInSeconds: 0 } }); jest.runOnlyPendingTimers(); expect(setTimeout).toHaveBeenCalledTimes(0); @@ -167,7 +167,7 @@ describe("PopupWindow", () => { it("shouldnt close the window after closePopupWindowAfter is less than 0", async () => { jest.spyOn(global, "setTimeout"); - new PopupWindow({ popupWindowFeatures: { closePopupWindowAfter: -120 } }); + new PopupWindow({ popupWindowFeatures: { closePopupWindowAfterInSeconds: -120 } }); jest.runOnlyPendingTimers(); expect(setTimeout).toHaveBeenCalledTimes(0); diff --git a/src/navigators/PopupWindow.ts b/src/navigators/PopupWindow.ts index b780e680e..275597157 100644 --- a/src/navigators/PopupWindow.ts +++ b/src/navigators/PopupWindow.ts @@ -7,6 +7,7 @@ import { AbstractChildWindow } from "./AbstractChildWindow"; import type { NavigateParams, NavigateResponse } from "./IWindow"; const checkForPopupClosedInterval = 500; +const second = 1000; /** * @public @@ -31,12 +32,12 @@ export class PopupWindow extends AbstractChildWindow { super(); const centeredPopup = PopupUtils.center({ ...DefaultPopupWindowFeatures, ...popupWindowFeatures }); this._window = window.open(undefined, popupWindowTarget, PopupUtils.serialize(centeredPopup)); - if (popupWindowFeatures?.closePopupWindowAfter && popupWindowFeatures?.closePopupWindowAfter > 0) { + if (popupWindowFeatures.closePopupWindowAfterInSeconds && popupWindowFeatures.closePopupWindowAfterInSeconds > 0) { setTimeout(() => { if (!this._window || typeof this._window.closed !== "boolean" || this._window.closed) { this._abort.raise(new Error("Popup blocked by user")); } - }, popupWindowFeatures.closePopupWindowAfter); + }, popupWindowFeatures.closePopupWindowAfterInSeconds * second); } } diff --git a/src/utils/PopupUtils.ts b/src/utils/PopupUtils.ts index d48291942..e1123ad82 100644 --- a/src/utils/PopupUtils.ts +++ b/src/utils/PopupUtils.ts @@ -14,8 +14,8 @@ export interface PopupWindowFeatures { status?: boolean | string; resizable?: boolean | string; scrollbars?: boolean | string; - /** Close popup window after time in milliseconds, by default it is 0. To enable this feature set value greater than 0 */ - closePopupWindowAfter?: number; + /** Close popup window after time in seconds, by default it is -1. To enable this feature set value greater than 0 */ + closePopupWindowAfterInSeconds?: number; [k: string]: boolean | string | number | undefined; }