Repository: authts/oidc-client-ts
Pull request #1146 (2023-08-25 16:44:25)

 #1134  Add possibility to automatically close popup window after som…
…e time

<!-- Please link relevant issue numbers or provide context for this change -->
Closes/fixes #issue

### Checklist

- [ ] This PR makes changes to the public API <!-- was the API report (docs/oidc-client-ts.api.md) updated by this PR? -->
- [ ] I have included links for closing relevant issue numbers


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts) Report
Patch coverage: **`71.42%`** and project coverage change: **`-0.03%`** :warning:
> Comparison is base [(`175ea70`)](https://app.codecov.io/gh/authts/oidc-client-ts/commit/175ea701eef61abfc8063e1e925ff8255060ec4b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts) 77.82% compared to head [(`f614cde`)](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts) 77.79%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1146      +/-   ##
==========================================
- Coverage   77.82%   77.79%   -0.03%     
==========================================
  Files          45       45              
  Lines        1718     1725       +7     
  Branches      341      344       +3     
==========================================
+ Hits         1337     1342       +5     
- Misses        341      343       +2     
  Partials       40       40              
```

| [Flag](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts) | `77.79% <71.42%> (-0.03%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files Changed](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts) | Coverage Δ | |
|---|---|---|
| [src/navigators/PopupWindow.ts](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts#diff-c3JjL25hdmlnYXRvcnMvUG9wdXBXaW5kb3cudHM=) | `81.48% <0.00%> (-6.52%)` | :arrow_down: |
| [src/utils/PopupUtils.ts](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts#diff-c3JjL3V0aWxzL1BvcHVwVXRpbHMudHM=) | `70.00% <ø> (ø)` | |
| [src/UserManagerSettings.ts](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts#diff-c3JjL1VzZXJNYW5hZ2VyU2V0dGluZ3MudHM=) | `98.43% <100.00%> (+0.13%)` | :arrow_up: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/authts/oidc-client-ts/pull/1146?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=authts).

pamapa: see https://github.com/authts/oidc-client-ts/pull/798

Patch:
diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md
index a7412c74..a7b7a237 100644
--- a/docs/oidc-client-ts.api.md
+++ b/docs/oidc-client-ts.api.md
@@ -480,6 +480,7 @@ export interface OidcStandardClaims {
 export interface PopupWindowFeatures {
     // (undocumented)
     [k: string]: boolean | string | number | undefined;
+    closePopupWindowAfterInSeconds?: number;
     // (undocumented)
     height?: number;
     // (undocumented)
diff --git a/src/UserManagerSettings.ts b/src/UserManagerSettings.ts
index e2c9045a..cf6ae366 100644
--- a/src/UserManagerSettings.ts
+++ b/src/UserManagerSettings.ts
@@ -10,6 +10,7 @@ export const DefaultPopupWindowFeatures: PopupWindowFeatures = {
     location: false,
     toolbar: false,
     height: 640,
+    closePopupWindowAfterInSeconds: -1,
 };
 export const DefaultPopupTarget = "_blank";
 const DefaultAccessTokenExpiringNotificationTimeInSeconds = 60;
@@ -28,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 \})
+     * (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.ts b/src/navigators/PopupWindow.ts
index f79dd786..27559715 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,6 +32,13 @@ export class PopupWindow extends AbstractChildWindow {
         super();
         const centeredPopup = PopupUtils.center({ ...DefaultPopupWindowFeatures, ...popupWindowFeatures });
         this._window = window.open(undefined, popupWindowTarget, PopupUtils.serialize(centeredPopup));
+        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.closePopupWindowAfterInSeconds * second);
+        }
     }
 
     public async navigate(params: NavigateParams): Promise<NavigateResponse> {
diff --git a/src/utils/PopupUtils.ts b/src/utils/PopupUtils.ts
index fcd2335a..e1123ad8 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;
+    /** 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;
 }


Test patch:
diff --git a/src/UserManagerSettings.test.ts b/src/UserManagerSettings.test.ts
index 310dc3c2..e1e3cb39 100644
--- a/src/UserManagerSettings.test.ts
+++ b/src/UserManagerSettings.test.ts
@@ -53,6 +53,19 @@ describe("UserManagerSettings", () => {
             expect(subject.popupWindowFeatures).toEqual({ status: true });
         });
 
+        it("should set closePopupWindowAfterInMilliseconds", () => {
+            // act
+            const closePopupWindowAfterInSeconds = 100;
+            const subject = new UserManagerSettingsStore({
+                authority: "authority",
+                client_id: "client",
+                redirect_uri: "redirect",
+                popupWindowFeatures: { status: true, closePopupWindowAfterInSeconds },
+            });
+
+            // assert
+            expect(subject.popupWindowFeatures).toEqual({ status: true, closePopupWindowAfterInSeconds });
+        });
     });
 
     describe("popupWindowTarget", () => {
diff --git a/src/navigators/PopupWindow.test.ts b/src/navigators/PopupWindow.test.ts
index 32211b80..aa2078a7 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: { closePopupWindowAfterInSeconds: 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: { closePopupWindowAfterInSeconds: 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: { closePopupWindowAfterInSeconds: -120 } });
+
+        jest.runOnlyPendingTimers();
+        expect(setTimeout).toHaveBeenCalledTimes(0);
+    });
 });