Skip to content

Commit

Permalink
[AUT-1539] - Child Window Pop up option (#46)
Browse files Browse the repository at this point in the history
* [AUT-1539] - Child Window Pop up option

* add window handler logic

* cleanup

* Rename consts/functions

* Tweak import

---------

Co-authored-by: Hamish Meikle <[email protected]>
  • Loading branch information
justinsoong and hwhmeikle committed Oct 29, 2023
1 parent ef06f74 commit 135bd78
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 34 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@authsignal/browser",
"version": "0.3.2",
"version": "0.3.3",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
Expand Down
98 changes: 72 additions & 26 deletions src/authsignal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
AuthsignalWindowMessage,
AuthsignalWindowMessageData,
LaunchOptions,
PopupLaunchOptions,
TokenPayload,
WindowLaunchOptions,
} from "./types";
import {PopupHandler} from "./popup-handler";
import {PopupHandler, WindowHandler} from "./handlers";
import {Passkey} from "./passkey";

const DEFAULT_COOKIE_NAME = "__as_aid";
Expand Down Expand Up @@ -57,41 +59,85 @@ export class Authsignal {

launch(url: string, options?: {mode?: "redirect"} & LaunchOptions): undefined;
launch(url: string, options?: {mode: "popup"} & LaunchOptions): Promise<TokenPayload>;
launch(url: string, options?: {mode: "window"} & LaunchOptions): Promise<TokenPayload>;
launch(url: string, options?: LaunchOptions) {
if (!options?.mode || options.mode === "redirect") {
window.location.href = url;
} else {
const {popupOptions} = options;
switch (options?.mode) {
case "window":
return this.launchWithWindow(url, options);
case "popup":
return this.launchWithPopup(url, options);
case "redirect":
default:
this.launchWithRedirect(url);
}
}

const Popup = new PopupHandler({width: popupOptions?.width});
private launchWithRedirect(url: string) {
window.location.href = url;
}

const popupUrl = `${url}&mode=popup`;
private launchWithPopup(url: string, options: PopupLaunchOptions) {
const {popupOptions} = options;

Popup.show({url: popupUrl});
const popupHandler = new PopupHandler({width: popupOptions?.width});

return new Promise<TokenPayload>((resolve) => {
const onMessage = (event: MessageEvent) => {
let data: AuthsignalWindowMessageData | null = null;
const popupUrl = `${url}&mode=popup`;

try {
data = JSON.parse(event.data) as AuthsignalWindowMessageData;
} catch {
// Ignore if the event data is not valid JSON
}
popupHandler.show({url: popupUrl});

if (data?.event === AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP) {
this._token = data.token;
return new Promise<TokenPayload>((resolve) => {
const onMessage = (event: MessageEvent) => {
let data: AuthsignalWindowMessageData | null = null;

Popup.close();
}
};
try {
data = JSON.parse(event.data) as AuthsignalWindowMessageData;
} catch {
// Ignore if the event data is not valid JSON
}

Popup.on("hide", () => {
resolve({token: this._token});
});
if (data?.event === AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP) {
this._token = data.token;

window.addEventListener("message", onMessage, false);
popupHandler.close();
}
};

popupHandler.on("hide", () => {
resolve({token: this._token});
});
}

window.addEventListener("message", onMessage, false);
});
}

private launchWithWindow(url: string, options: WindowLaunchOptions) {
const {windowOptions} = options;

const windowHandler = new WindowHandler();

const windowUrl = `${url}&mode=popup`;

windowHandler.show({url: windowUrl, width: windowOptions?.width, height: windowOptions?.height});

return new Promise<TokenPayload>((resolve) => {
const onMessage = (event: MessageEvent) => {
let data: AuthsignalWindowMessageData | null = null;

try {
data = JSON.parse(event.data) as AuthsignalWindowMessageData;
} catch {
// Ignore if the event data is not valid JSON
}

if (data?.event === AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP) {
this._token = data.token;

windowHandler.close();
resolve({token: this._token});
}
};

window.addEventListener("message", onMessage, false);
});
}
}
2 changes: 2 additions & 0 deletions src/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {WindowHandler} from "./window-handler";
export {PopupHandler} from "./popup-handler";
4 changes: 1 addition & 3 deletions src/popup-handler.ts → src/handlers/popup-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type PopupHandlerOptions = {
width?: string;
};

class PopupHandler {
export class PopupHandler {
private popup: A11yDialog | null = null;

constructor({width}: PopupHandlerOptions) {
Expand Down Expand Up @@ -174,5 +174,3 @@ function resizeIframe(event: MessageEvent) {
iframeEl.style.height = event.data.height + "px";
}
}

export {PopupHandler};
54 changes: 54 additions & 0 deletions src/handlers/window-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
type WindowShowInput = {
url: string;
width?: number;
height?: number;
};

const DEFAULT_WIDTH = 400;
const DEFAULT_HEIGHT = 500;

export class WindowHandler {
private windowRef: Window | null = null;

show({url, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT}: WindowShowInput) {
const windowRef = openWindow({url, width, height, win: window});

if (!windowRef) {
throw new Error("Window is not initialized");
}

this.windowRef = windowRef;

return windowRef;
}

close() {
if (!this.windowRef) {
throw new Error("Window is not initialized");
}

this.windowRef.close();
}
}

type OpenWindowInput = {
url: string;
width: number;
height: number;
win: Window;
};

function openWindow({url, width, height, win}: OpenWindowInput): Window | null {
if (!win.top) {
return null;
}

const y = win.top.outerHeight / 2 + win.top.screenY - height / 2;
const x = win.top.outerWidth / 2 + win.top.screenX - width / 2;

return window.open(
url,
"",
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${y}, left=${x}`
);
}
16 changes: 12 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ type BaseLaunchOptions = {
* will trigger a full page redirect.
* If no value is supplied, mode defaults to `redirect`.
*/
mode?: "popup" | "redirect";
mode?: "popup" | "redirect" | "window";
};

type RedirectLaunchOptions = BaseLaunchOptions & {
export type RedirectLaunchOptions = BaseLaunchOptions & {
mode: "redirect";
};

type PopupLaunchOptions = BaseLaunchOptions & {
export type PopupLaunchOptions = BaseLaunchOptions & {
mode: "popup";
popupOptions?: {
/** Any valid CSS value for the `width` property. */
Expand All @@ -24,7 +24,15 @@ type PopupLaunchOptions = BaseLaunchOptions & {
};
};

export type LaunchOptions = RedirectLaunchOptions | PopupLaunchOptions;
export type WindowLaunchOptions = BaseLaunchOptions & {
mode: "window";
windowOptions?: {
width?: number;
height?: number;
};
};

export type LaunchOptions = RedirectLaunchOptions | PopupLaunchOptions | WindowLaunchOptions;

export type AuthsignalOptions = {
/**
Expand Down

0 comments on commit 135bd78

Please sign in to comment.