Skip to content

Commit

Permalink
Popup Abort Signals (#1626)
Browse files Browse the repository at this point in the history
* allow popup to abort
  • Loading branch information
smujmaiku committed Aug 20, 2024
1 parent 448c76b commit ad05ded
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/oidc-client-ts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ export interface PopupWindowFeatures {

// @public (undocumented)
export interface PopupWindowParams {
popupSignal?: AbortSignal | null;
// (undocumented)
popupWindowFeatures?: PopupWindowFeatures;
// (undocumented)
Expand Down
6 changes: 4 additions & 2 deletions src/UserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,14 +253,15 @@ export class UserManager {
const {
popupWindowFeatures,
popupWindowTarget,
popupSignal,
...requestArgs
} = args;
const url = this.settings.popup_redirect_uri;
if (!url) {
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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions src/navigators/PopupNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PopupWindow> {
return new PopupWindow({ popupWindowFeatures, popupWindowTarget });
return new PopupWindow({ popupWindowFeatures, popupWindowTarget, popupSignal });
}

public async callback(url: string, { keepOpen = false }): Promise<void> {
Expand Down
12 changes: 12 additions & 0 deletions src/navigators/PopupWindow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
10 changes: 10 additions & 0 deletions src/navigators/PopupWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const second = 1000;
export interface PopupWindowParams {
popupWindowFeatures?: PopupWindowFeatures;
popupWindowTarget?: string;
/** An AbortSignal to set request's signal. */
popupSignal?: AbortSignal | null;
}

/**
Expand All @@ -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) {
Expand Down

0 comments on commit ad05ded

Please sign in to comment.