diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index 838553e60..41faf371d 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -581,6 +581,7 @@ export interface PopupWindowFeatures { // @public (undocumented) export interface PopupWindowParams { + popupSignal?: AbortSignal | null; // (undocumented) popupWindowFeatures?: PopupWindowFeatures; // (undocumented) diff --git a/src/UserManager.ts b/src/UserManager.ts index aafe47c52..7f3ab43f7 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -253,6 +253,7 @@ export class UserManager { const { popupWindowFeatures, popupWindowTarget, + popupSignal, ...requestArgs } = args; const url = this.settings.popup_redirect_uri; @@ -260,7 +261,7 @@ export class UserManager { logger.throw(new Error("No popup_redirect_uri configured")); } - const handle = await this._popupNavigator.prepare({ popupWindowFeatures, popupWindowTarget }); + const handle = await this._popupNavigator.prepare({ popupWindowFeatures, popupWindowTarget, popupSignal }); const user = await this._signin({ request_type: "si:p", redirect_uri: url, @@ -597,11 +598,12 @@ export class UserManager { const { popupWindowFeatures, popupWindowTarget, + popupSignal, ...requestArgs } = args; const url = this.settings.popup_post_logout_redirect_uri; - const handle = await this._popupNavigator.prepare({ popupWindowFeatures, popupWindowTarget }); + const handle = await this._popupNavigator.prepare({ popupWindowFeatures, popupWindowTarget, popupSignal }); await this._signout({ request_type: "so:p", post_logout_redirect_uri: url, diff --git a/src/navigators/PopupNavigator.ts b/src/navigators/PopupNavigator.ts index 11c80fcef..d9875fb75 100644 --- a/src/navigators/PopupNavigator.ts +++ b/src/navigators/PopupNavigator.ts @@ -12,13 +12,14 @@ import type { UserManagerSettingsStore } from "../UserManagerSettings"; export class PopupNavigator implements INavigator { private readonly _logger = new Logger("PopupNavigator"); - constructor(private _settings: UserManagerSettingsStore) {} + constructor(private _settings: UserManagerSettingsStore) { } public async prepare({ popupWindowFeatures = this._settings.popupWindowFeatures, popupWindowTarget = this._settings.popupWindowTarget, + popupSignal, }: PopupWindowParams): Promise { - return new PopupWindow({ popupWindowFeatures, popupWindowTarget }); + return new PopupWindow({ popupWindowFeatures, popupWindowTarget, popupSignal }); } public async callback(url: string, { keepOpen = false }): Promise { diff --git a/src/navigators/PopupWindow.test.ts b/src/navigators/PopupWindow.test.ts index 6a203f8a7..73b169cc3 100644 --- a/src/navigators/PopupWindow.test.ts +++ b/src/navigators/PopupWindow.test.ts @@ -141,6 +141,18 @@ describe("PopupWindow", () => { jest.runAllTimers(); }); + it("should reject when the window is aborted by signal", async () => { + const customReason = "Custom reason"; + const controller = new AbortController(); + const popupWindow = new PopupWindow({ popupSignal: controller.signal }); + + const promise = popupWindow.navigate({ url: "http://sts/authorize?x=y", state: "someid" }); + controller.abort(customReason); + + await expect(promise).rejects.toThrow(customReason); + jest.runAllTimers(); + }); + it("should notify the parent window", async () => { PopupWindow.notifyOpener("http://sts/authorize?x=y", false); expect((window.opener as WindowProxy).postMessage).toHaveBeenCalledWith({ diff --git a/src/navigators/PopupWindow.ts b/src/navigators/PopupWindow.ts index 4f5144e36..6f626aa32 100644 --- a/src/navigators/PopupWindow.ts +++ b/src/navigators/PopupWindow.ts @@ -15,6 +15,8 @@ const second = 1000; export interface PopupWindowParams { popupWindowFeatures?: PopupWindowFeatures; popupWindowTarget?: string; + /** An AbortSignal to set request's signal. */ + popupSignal?: AbortSignal | null; } /** @@ -28,10 +30,18 @@ export class PopupWindow extends AbstractChildWindow { public constructor({ popupWindowTarget = DefaultPopupTarget, popupWindowFeatures = {}, + popupSignal, }: PopupWindowParams) { super(); const centeredPopup = PopupUtils.center({ ...DefaultPopupWindowFeatures, ...popupWindowFeatures }); this._window = window.open(undefined, popupWindowTarget, PopupUtils.serialize(centeredPopup)); + + if (popupSignal) { + popupSignal.addEventListener("abort", () => { + void this._abort.raise(new Error(popupSignal.reason ?? "Popup aborted")); + }); + } + if (popupWindowFeatures.closePopupWindowAfterInSeconds && popupWindowFeatures.closePopupWindowAfterInSeconds > 0) { setTimeout(() => { if (!this._window || typeof this._window.closed !== "boolean" || this._window.closed) {